<font size='+3' color='#dd0000'>
    <b>The SpecAccs Murder Conspiracy!</b>
</font>
<br>
<font size='+2.5' color='#FBAB60'>
    <b>A Short Data Analytic Comic</b>
</font>
# Importing R libraries

library(reticulate)
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ───────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.5     ✓ purrr   0.3.4
✓ tibble  3.1.5     ✓ dplyr   1.0.7
✓ tidyr   1.1.4     ✓ stringr 1.4.0
✓ readr   2.0.2     ✓ forcats 0.5.1
── Conflicts ──────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(multidplyr)
library(fitdistrplus)
Loading required package: MASS

Attaching package: ‘MASS’

The following object is masked from ‘package:dplyr’:

    select

Loading required package: survival
library(knitr)
library(readxl)
library(kableExtra)
Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio

Attaching package: ‘kableExtra’

The following object is masked from ‘package:dplyr’:

    group_rows
library(clipr)
Welcome to clipr. See ?write_clip for advisories on writing to the clipboard in R.
library(scales)

Attaching package: ‘scales’

The following object is masked from ‘package:purrr’:

    discard

The following object is masked from ‘package:readr’:

    col_factor
#install.packages("ggpubr")
library(ggpubr)
library(hrbrthemes)
NOTE: Either Arial Narrow or Roboto Condensed fonts are required to use these themes.
      Please use hrbrthemes::import_roboto_condensed() to install Roboto Condensed and
      if Arial Narrow is not on your system, please see https://bit.ly/arialnarrow
library(plotly)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘plotly’

The following object is masked from ‘package:MASS’:

    select

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
library(reshape2)

Attaching package: ‘reshape2’

The following object is masked from ‘package:tidyr’:

    smiths
library(devtools)
Loading required package: usethis
devtools::install_github("ellisp/frs-r-package/pkg")
Skipping install of 'frs' from a github remote, the SHA1 (884117fe) has not changed since last install.
  Use `force = TRUE` to force installation
library(frs)
Loading required package: rmarkdown

Attaching package: ‘frs’

The following object is masked from ‘package:scales’:

    modulus_trans
python_cmds <- "
# Importing data processing libraries
import numpy as np
import pandas as pd
"
py_run_string(python_cmds)
python_cmds <- "
# Loading datasets
#   1. Kaggle ML & DL Survey 2021
#   2. Big Mac Index
#   3. Graduate Age Dataset
#   4. GPU Pricing Dataset

# Kaggle ML & DL Survey 2021 Data
#   Info: 2021
#   Link: https://www.kaggle.com/c/kaggle-survey-2021/data
def data_processing_func(data):
    cols = [*data.columns]
    group_cols = [x for x in cols if 'Selected Choice' in x]
    group_cols_main_ques = [x.split(' - Selected Choice')[0] for x in group_cols]
    group_cols_uniq_main_ques = np.unique(group_cols_main_ques)
    
    def group_cols_func(x):
        answer_list = []
        for col in x.keys():
            if(pd.isnull(x[col]) == False):
                answer_list.append(x[col])
        return ' | '.join(answer_list)

    grouped_cols_df = pd.DataFrame()
    for ques_idx, ques in enumerate(group_cols_uniq_main_ques):
        ques_group_cols = [x for x in group_cols if x.startswith(ques)]
        grouped_col_df = data[ques_group_cols].apply(group_cols_func, axis = 1)
        grouped_cols_df = pd.concat([grouped_cols_df, grouped_col_df], axis = 1)
    grouped_cols_df.columns = group_cols_uniq_main_ques
    
    data = data.drop(group_cols, axis = 1)
    data = pd.concat([data, grouped_cols_df], axis = 1)
    return data
raw_data = pd.read_csv('../Data/kaggle-survey-2021/kaggle_survey_2021_responses.csv', header = [0], skiprows = [0])
data = data_processing_func(raw_data)
data_questions = data.columns
data.columns = [f'Q_{x}' for x in range(data.shape[1])]
raw_data['kaggler_id'] = pd.Series(np.arange(raw_data.shape[0]))
data['kaggler_id'] = pd.Series(np.arange(data.shape[0]))

# Big Mac Index
#   Info: GDP-adjusted, Jul 2021
#   Link: https://www.economist.com/big-mac-index
bigmac_idx = {
    'India': -18.4,
    'Indonesia': -26.8,
    'Pakistan': 16.5,
    'Mexico': -6.1,
    'Russia': -34.3,
    'Turkey': -31.1,
    'Australia': -8.1,
    'Nigeria': np.nan,
    'Greece': 9.2,
    'Belgium': 9.2,
    'Japan': -24.4,
    'Egypt': -14.9,
    'Singapore': -21.1,
    'Brazil': 31.5,
    'Poland': -6.7,
    'China': -0.4,
    'Iran, Islamic Republic of...': np.nan,
    'United States of America': 0,
    'Italy': 9.2,
    'Viet Nam': -5.8,
    'Israel': 6.7,
    'Peru': -0.7,
    'South Africa': -29.6,
    'Other': np.nan,
    'Spain': 9.2,
    'Bangladesh': np.nan,
    'United Kingdom of Great Britain and Northern Ireland': 1.0,
    'France': 9.2,
    'Switzerland': 6.5,
    'Algeria': np.nan,
    'Tunisia': np.nan,
    'Argentina': 16.3,
    'Sweden': 19.8,
    'Colombia': 3.5,
    'I do not wish to disclose my location': np.nan,
    'Canada': 10.2,
    'Chile': 10.2,
    'Netherlands': 9.2,
    'Ukraine': -25.1,
    'Saudi Arabia': -3.5,
    'Romania': -29.0,
    'Morocco': np.nan,
    'Austria': 9.2,
    'Taiwan': -38.9,
    'Kenya': np.nan,
    'Belarus': np.nan,
    'Ireland': 9.2,
    'Portugal': 9.2,
    'Hong Kong (S.A.R.)': -45.6,
    'Denmark': -14.2,
    'Germany': 9.2,
    'South Korea': -7.8,
    'Philippines': -11.2,
    'Sri Lanka': 10.0,
    'United Arab Emirates': -7.8,
    'Uganda': np.nan,
    'Ghana': np.nan,
    'Malaysia': -31.7,
    'Thailand': 17.0,
    'Nepal': np.nan,
    'Kazakhstan': np.nan,
    'Ethiopia': np.nan,
    'Iraq': np.nan,
    'Ecuador': np.nan,
    'Norway': 8.6,
    'Czech Republic': 2.9,
}

# Graduate Age Dataset
#   Info: 2020-21, UG & PG students
#   Link: https://hea.ie/statistics/data-for-download-and-visualisations/enrolments/key-facts-figures-2020-2021/
def get_student_age_data():
    data = pd.read_csv('../Data/student_age.csv')
    ug_data = data[data['Course'] == 'UG']
    pg_data = data[data['Course'] == 'PG']
    pg_data = pg_data.assign(Neg_Composition = -pg_data['Composition'])
    return ug_data, pg_data
ug_data, pg_data = get_student_age_data()

# GPU Pricing Dataset
#   Info: 2018
#   Link: https://www.kaggle.com/raczeq/ethereum-effect-pc-parts
def get_gpu_prices():
    data = pd.read_csv('../Data/gpu_prices/FACT_GPU_PRICE.csv')
    time_data = pd.read_csv('../Data/gpu_prices/DIM_TIME.csv')
    prod_data = pd.read_csv('../Data/gpu_prices/DIM_GPU_PROD.csv')
    region_data = pd.read_csv('../Data/gpu_prices/DIM_REGION.csv')
    prod_data['Memory_Capacity_category'] = prod_data['Memory_Capacity'].apply(lambda x: 'high' if x >= 10 else 'medium' if x >=2 else 'low')
    merged_data = pd.merge(data, time_data, how = 'left', left_on = 'TimeId', right_on = 'Id')
    merged_data = pd.merge(merged_data, prod_data, how = 'left', left_on = 'ProdId', right_on = 'Id')
    merged_data = pd.merge(merged_data, region_data, how = 'left', left_on = 'RegionId', right_on = 'Id')

    country_code_name_map = {
        'au': 'Australia',
        'be': 'Belgium',
        'ca': 'Canada',
        'de': 'Germany',
        'es': 'Spain',
        'fr': 'France',
        'ie': 'Ireland',
        'it': 'Italy',
        'nz': 'New Zealand',
        'uk': 'United Kingdom of Great Britain and Northern Ireland',
        'us': 'United States of America'
    }
    merged_data['Code'] = merged_data['Code'].map(country_code_name_map)
    merged_data = merged_data[merged_data['Code'] != 'New Zealand']
    merged_data['BigMac_index'] = merged_data['Code'].map(bigmac_idx)
    merged_data['ppp_price'] = merged_data['Price_USD'] * (1 + (merged_data['BigMac_index'] / 100.0))
    return merged_data
gpu_prices_data = get_gpu_prices()
gpu_prices_data = gpu_prices_data[gpu_prices_data['Year'] == 2018]
"
py_run_string(python_cmds)
sys:1: DtypeWarning: Columns (195,201) have mixed types.Specify dtype option on import or set low_memory=False.
# Graphing Utils

black <- '#240115'
light_blue <- '#4cbefd'
dark_blue <- '#114b5f'
purple <- '#6457a6'
green <- '#99ffd1'
dark_green <- "#3E885B"
red <- '#dd0000'
white <- '#fae1df'

basic_plot_dec <- function() {
  theme(
    plot.background = element_rect(fill = black),
    panel.background = element_rect(fill = black),
    panel.grid.minor = element_line(color = alpha(black, 0)),
    plot.title = element_text(color = white),
    plot.subtitle = element_text(color = white),
    plot.caption = element_text(color = white),
    axis.title.x = element_text(color = white),
    axis.title.y = element_text(color = white),
    axis.text.x = element_text(color = alpha(white, 0.75)),
    axis.text.y = element_text(color = alpha(white, 0.75))
  )
}

basic_color_dec <- function(color) {
  theme(
    plot.title = element_text(color = alpha(color, 0.75))
  )
}

basic_orient_dec <- function(inv) {
  if(missing(inv)) inv <- FALSE
  if(inv) {
    theme(
      panel.grid.major.y = element_line(color = alpha(black, 0)),
      panel.grid.major.x = element_line(color = alpha(white, 0.25)),
      axis.text.y = element_text(hjust = 1)
    )
  }
  else {
    theme(
      panel.grid.major.x = element_line(color = alpha(black, 0)),
      panel.grid.major.y = element_line(color = alpha(white, 0.25)),
      axis.text.x = element_text(angle = 90, hjust = 1)
    )
  }
}

fig <- function(width, heigth){
     options(repr.plot.width = width, repr.plot.height = heigth)
}
python_cmds <- "
def plot_uniq_vals_in_ques(ques):
    data = raw_data.set_index('kaggler_id')
    sel_cols = [x for x in data.columns if ques in x]
    val_cnts = (data.shape[0] - pd.isnull(data[sel_cols]).sum().sort_index()) / data.shape[0] * 100
    val_cnts_x = [x.split('Selected Choice - ')[-1] for x in val_cnts.index]
    val_cnts_y = val_cnts
    return val_cnts_x, val_cnts_y
val_x, val_y = plot_uniq_vals_in_ques('Which types of specialized hardware do you use on a regular basis?')
"
py_run_string(python_cmds)

val_plot_df <- as.data.frame(list(py$val_x, py$val_y))
rownames(val_plot_df) <- NULL
colnames(val_plot_df) <- c('Name', 'Percent')

g <- ggplot(data = val_plot_df, aes(y = Name, x = Percent)) + geom_bar(stat = 'identity', colour = dark_blue, fill = light_blue, alpha = 0.6)
g <- g + 
  basic_plot_dec() + 
  basic_color_dec(light_blue) + 
  basic_orient_dec(TRUE) + 
  ggtitle("Usage of Specialized Accelerators") +
  xlab("Percentage of Kaggler using Spec Acc (%)") +
  ylab("Spec Acc Type")
g

python_cmds <- "
# TODO in R
# Print details about size of processed dataset & number of excluded columns
sel_cols = [x for x in raw_data.columns if 'Which types of specialized hardware do you use on a regular basis?' in x]
excl_index = pd.isnull(raw_data[sel_cols]).sum(axis = 1)
excl_index = excl_index[excl_index == 6].index
print(f'No. of Excluded Kagglers: {len(excl_index)}')
new_data = data.drop(excl_index, axis = 0)
print(f'Size of processed data: {new_data.shape}')
new_data['has_spec_acc'] = new_data['Q_50'].apply(lambda x: x != 'None')
new_data['num_spec_acc'] = new_data['Q_50'].apply(lambda x: 0 if x == 'None' else len(x.split('|')))
"
py_run_string(python_cmds)
No. of Excluded Kagglers: 1391
Size of processed data: (24582, 53)
python_cmds <- "
salary_order = [
    '$0-999',
    '1,000-1,999',
    '2,000-2,999',
    '3,000-3,999',
    '4,000-4,999',
    '5,000-7,499',
    '7,500-9,999',
    '10,000-14,999',
    '15,000-19,999',
    '20,000-24,999',
    '25,000-29,999',
    '30,000-39,999',
    '40,000-49,999',
    '50,000-59,999',
    '60,000-69,999',
    '70,000-79,999',
    '80,000-89,999',
    '90,000-99,999',
    '100,000-124,999',
    '125,000-149,999',
    '150,000-199,999',
    '200,000-249,999',
    '250,000-299,999',
    '300,000-499,999',
    '$500,000-999,999',
    '>$1,000,000'
]
salaries = new_data['Q_10'].value_counts().loc[salary_order]
"
py_run_string(python_cmds)
salary_plot_df <- as.data.frame(list(py$salary_order, py$salaries))
rownames(salary_plot_df) <- NULL
colnames(salary_plot_df) <- c("Salary", "Count")
salary_plot_df$Salary = factor(salary_plot_df$Salary, levels = py$salary_order)

fig(4, 30)
g1 <- ggplot(data = salary_plot_df, aes(x = Salary, y = Count)) + geom_bar(stat = 'identity', colour = dark_blue, fill = light_blue, alpha = 0.6)
g1 <- g1 +
  basic_plot_dec() +
  basic_color_dec(light_blue) +
  basic_orient_dec() +
  labs(title = "Unequal Bin Widths") +
  xlab("Salary Bins") +
  ylab("Count") +
  theme(
    axis.text.x = element_text(size = 8)
  )
g1

python_cmds <- "
starting_points = [0   , 1000, 2000, 3000, 4000, 5000, 7500, 10000, 15000, 20000, 25000, 30000, 40000, 50000, 60000, 70000, 80000, 90000, 100000, 125000, 150000, 200000, 250000, 300000, 500000, 1000000]
widths =          [1000, 1000, 1000, 1000, 1000, 2500, 2500,  5000,  5000,  5000,  5000, 10000, 10000, 10000, 10000, 10000, 10000, 10000,  25000,  25000,  50000,  50000,  50000, 200000, 500000, 1000000]
ending_points = np.add(starting_points, widths)
mid_points = np.add(starting_points, np.divide(widths, 2))
"
py_run_string(python_cmds)
salary_plot_df <- as.data.frame(list(py$mid_points, py$salaries))
rownames(salary_plot_df) <- NULL
colnames(salary_plot_df) <- c("Salary", "Count")
widths <- py$widths

fig(4, 30)
g2 <- ggplot(data = salary_plot_df, aes(x = Salary, y = Count)) + geom_bar(stat = 'identity', colour = black, fill = green, alpha = 0.6, width = widths)
g2 <- g2 +
  basic_plot_dec() +
  basic_color_dec(green) +
  basic_orient_dec() +
  labs(title = "Salary Distribution", subtitle = "True Distribution", caption = "(Salary limited to $250K)") +
  xlab("Salary") +
  ylab("Count") +
  theme(
    axis.text.x = element_text(size = 8),
    aspect.ratio = 4/15
  ) +
  xlim(0, 250000)
g2
Warning: Removed 4 rows containing missing values (position_stack).

python_cmds <- "
new_ppp_data = new_data.copy()
new_ppp_data['PPP_adjusted'] = new_data['Q_2'].map(bigmac_idx)
new_ppp_data = new_ppp_data.loc[~pd.isnull(new_ppp_data['PPP_adjusted'])]
new_ppp_data = new_ppp_data.loc[~pd.isnull(new_ppp_data['Q_10'])]
print(f'Shape of data after excluding countries for which we do not have PPP data & NAs in Q_10: {new_ppp_data.shape}')

new_ppp_data['min_sal'] = new_ppp_data['Q_10'].apply(lambda x: starting_points[salary_order.index(x)]) / 1000000
new_ppp_data['mid_sal'] = new_ppp_data['Q_10'].apply(lambda x: mid_points[salary_order.index(x)]) / 1000000
new_ppp_data['max_sal'] = new_ppp_data['Q_10'].apply(lambda x: ending_points[salary_order.index(x)]) / 1000000
new_ppp_data['PPP_adj_min_sal'] = new_ppp_data['min_sal'] * (1 + (new_ppp_data['PPP_adjusted'] / 100.0))
new_ppp_data['PPP_adj_mid_sal'] = new_ppp_data['mid_sal'] * (1 + (new_ppp_data['PPP_adjusted'] / 100.0))
new_ppp_data['PPP_adj_max_sal'] = new_ppp_data['max_sal'] * (1 + (new_ppp_data['PPP_adjusted'] / 100.0))
new_ppp_data_only_adj = new_ppp_data[['PPP_adj_min_sal', 'PPP_adj_max_sal']]
"
py_run_string(python_cmds)
Shape of data after excluding countries for which we do not have PPP data & NAs in Q_10: (12522, 56)
colnames(py$new_ppp_data_only_adj) <- c('left', 'right')
fitted_distribution_gamma <- fitdistcens(py$new_ppp_data_only_adj, "gamma")

# overall fit:
print("Fitted Parameters:")
[1] "Fitted Parameters:"
print(fitted_distribution_gamma$estimate)
    shape      rate 
0.3619788 7.6292501 
# Mean
midpt_mean <- py$new_ppp_data_only_adj %>%
  mutate(mid = (left + replace_na(right, 2)) / 2) %>%
  summarise(crude_mean = mean(mid)) %>%
  pull(crude_mean)
print(sprintf("Midpoint-based Mean: %f", midpt_mean))
[1] "Midpoint-based Mean: 0.049612"
estim_mean <- fitted_distribution_gamma$estimate["shape"] / fitted_distribution_gamma$estimate["rate"]
print(sprintf("Etimated Mean: %f", estim_mean))
[1] "Etimated Mean: 0.047446"
alpha <- fitted_distribution_gamma$estimate[['shape']]
beta <- fitted_distribution_gamma$estimate[['rate']]

breaks <- c(0, 0.25, 0.5, 0.75, 1)
g <- ggplot(py$new_ppp_data_only_adj) + stat_function(fun = dgamma, args = fitted_distribution_gamma$estimate, colour = green) + scale_x_continuous(breaks = breaks, labels = sapply(breaks, function(x) { paste('$', x * 1000000 / 1000, 'K', sep = '') }))
g <- g +
  basic_plot_dec() +
  basic_color_dec(green) +
  basic_orient_dec() +
  labs(title = "Fitted Gamma Distribution", subtitle = sprintf("Alpha=%.2f, Beta=%.2f", alpha, beta)) +
  xlab("Salary") +
  ylab("Distribution")
print(g)

python_cmds = "
ppp_bins = [[*x] for x in [*new_ppp_data.groupby(['PPP_adj_min_sal', 'PPP_adj_max_sal'])['kaggler_id'].aggregate('count').index]]
ppp_bins = pd.DataFrame(ppp_bins, columns = ['min', 'max'])
"
py_run_string(python_cmds)

find_area_mid <- function(min, max, alpha, beta) {
  f_1 <- pgamma(min, shape = alpha, rate= beta)
  f_2 <- pgamma(max, shape = alpha, rate = beta)
  qgamma((f_1 + f_2) / 2.0, shape = alpha, rate = beta)
}

find_actual_mid <- function(min, max, alpha, beta) {
  samples <- rgamma(1000000, shape = alpha, rate = beta)
  actual_mids <- c()
  for (idx in 1:length(min)) {
    actual_mids <- c(actual_mids, mean(samples[(samples >= min[idx]) & (samples <= max[idx])]))
  }
  actual_mids
}

py$ppp_bins <- py$ppp_bins %>%
  mutate(final_mid = find_actual_mid(min, max, alpha, beta))

python_cmds = "
new_ppp_data = pd.merge(new_ppp_data, ppp_bins, how = 'inner', left_on = ['PPP_adj_min_sal', 'PPP_adj_max_sal'], right_on = ['min', 'max'])
"
py_run_string(python_cmds)
fig(8, 30)
g <- ggplot(data = py$new_ppp_data) +
  geom_density(aes(x = mid_sal * 1000, colour = "Original"), show.legend = FALSE) +
  stat_density(aes(x = mid_sal * 1000, colour = "Original"), geom = 'line', position = 'identity') +
  geom_density(aes(x = final_mid * 1000, colour = "Adjusted"), show.legend = FALSE) +
  stat_density(aes(x = final_mid * 1000, colour = "Adjusted"), geom = 'line', position = 'identity') +
  xlim(0, 750)
g +
  basic_plot_dec() +
  basic_color_dec(green) +
  basic_orient_dec() +
  labs(title = "Impact of Distribution Fitting", colour = "Distribution Type", caption = "(Limiting salary to $750K)") +
  xlab("Salary (in 100K$)") +
  ylab("Density") +
  theme(
    legend.title = element_text(colour = white, face = 'bold'),
    legend.text = element_text(colour = white),
    legend.background = element_rect(fill = alpha(green, 0.5)),
    legend.key = element_rect(fill = alpha(green, 0)),
    aspect.ratio = 7/15,
    legend.position = c(0.85, 0.9),
  ) +
  scale_colour_manual(values = c("Original" = alpha(light_blue, 0.6), "Adjusted" = alpha(red, 0.6)))
Warning: Removed 51 rows containing non-finite values (stat_density).
Warning: Removed 51 rows containing non-finite values (stat_density).
Warning: Removed 51 rows containing non-finite values (stat_density).
Warning: Removed 51 rows containing non-finite values (stat_density).

python_cmds = "
pivot_data = pd.pivot_table(new_data.groupby(['has_spec_acc', 'Q_10'])[['kaggler_id']].aggregate('count'), values = 'kaggler_id', index = ['Q_10'], columns = ['has_spec_acc'])
for col in pivot_data.columns:
    pivot_data[col] = pivot_data[col] / pivot_data[col].sum() * 100
pivot_data = pivot_data.loc[salary_order]

has_no_spec_acc = new_ppp_data[new_ppp_data['has_spec_acc'] == 0]
has_spec_acc = new_ppp_data[new_ppp_data['has_spec_acc'] == 1]
has_no_spec_acc_only_adj = has_no_spec_acc[['PPP_adj_min_sal', 'PPP_adj_max_sal']]
has_spec_acc_only_adj = has_spec_acc[['PPP_adj_min_sal', 'PPP_adj_max_sal']]
"
py_run_string(python_cmds)

colnames(py$has_no_spec_acc_only_adj) <- c('left', 'right')
fitted_distribution_gamma <- fitdistcens(py$has_no_spec_acc_only_adj, "gamma")
alpha1 <- fitted_distribution_gamma$estimate[['shape']]
beta1 <- fitted_distribution_gamma$estimate[['rate']]

colnames(py$has_spec_acc_only_adj) <- c('left', 'right')
fitted_distribution_gamma <- fitdistcens(py$has_spec_acc_only_adj, "gamma")
alpha2 <- fitted_distribution_gamma$estimate[['shape']]
beta2 <- fitted_distribution_gamma$estimate[['rate']]
breaks <- c(0, 0.1, 0.2, 0.3, 0.4, 0.5)
g <- ggplot(py$new_ppp_data_only_adj) +
  stat_function(fun = dgamma, args = c(shape = alpha1, rate = beta1), aes(colour = "Has no SpecAccs")) +
  stat_function(fun = dgamma, args = c(shape = alpha2, rate = beta2), aes(colour = "Has SpecAccs")) +
  scale_x_continuous(breaks = breaks, labels = sapply(breaks, function(x) { x * 1000 }), limits = c(0, 0.5)) +
  scale_colour_manual(values = c("Has no SpecAccs" = alpha(green, 0.6), "Has SpecAccs" = alpha(red, 0.6)))
g <- g +
  basic_plot_dec() +
  basic_color_dec(light_blue) +
  basic_orient_dec() +
  labs(title = "SpecAccs Usage vs. Salary", colour = "SpecAccs Usage") +
  xlab("Salary") +
  ylab("Distribution") +
  theme(
    legend.title = element_text(colour = white, face = 'bold'),
    legend.text = element_text(colour = white),
    legend.background = element_rect(fill = alpha(light_blue, 0.5)),
    legend.key = element_rect(fill = alpha(light_blue, 0)),
    legend.position = c(0.85, 0.9),
  )
print(g)

python_cmds = "
has_no_spec_acc_salaries = has_no_spec_acc['Q_10'].value_counts().loc[salary_order]
has_no_spec_acc_salaries_x_vals = has_no_spec_acc_salaries.values / has_no_spec_acc_salaries.values.sum() * 100
has_spec_acc_salaries = has_spec_acc['Q_10'].value_counts().loc[salary_order]
has_spec_acc_salaries_x_vals = has_spec_acc_salaries.values / has_spec_acc_salaries.values.sum() * 100
diff = has_spec_acc_salaries_x_vals - has_no_spec_acc_salaries_x_vals
"
py_run_string(python_cmds)

spec_acc_salaries_plot_df <- as.data.frame(list(py$mid_points, py$has_spec_acc_salaries_x_vals, py$has_no_spec_acc_salaries_x_vals))
rownames(spec_acc_salaries_plot_df) <- NULL
colnames(spec_acc_salaries_plot_df) <- c("Salary", "HasSpecAccsCount", "HasNoSpecAccsCount")
widths <- py$widths

fig(4, 30)
g <- ggplot(data = spec_acc_salaries_plot_df, aes(x = Salary)) +
  geom_bar(aes(y = HasSpecAccsCount, fill = 'HasSpecAccs'), stat = 'identity', alpha = 0.5, width = widths) +
  geom_bar(aes(y = HasNoSpecAccsCount, fill = 'HasNoSpecAccs'), stat = 'identity', alpha = 0.5, width = widths) +
  scale_fill_manual(values = c("HasSpecAccs" = alpha(light_blue, 0.6), "HasNoSpecAccs" = alpha(red, 0.6)))
g <- g +
  basic_plot_dec() +
  basic_color_dec(green) +
  basic_orient_dec() +
  labs(title = "Salary Distribution", subtitle = "True Distribution", caption = "(Salary limited to $250K)", fill = "SpecAccs Usage") +
  xlab("Salary") +
  ylab("Count") +
  theme(
    axis.text.x = element_text(size = 8),
    legend.title = element_text(colour = white, face = 'bold'),
    legend.text = element_text(colour = white),
    legend.background = element_rect(fill = alpha(green, 0.5)),
    legend.key = element_rect(fill = alpha(green, 0)),
    legend.position = c(0.85, 0.9),
  )
print(g)


spec_acc_salaries_plot_df <- as.data.frame(list(py$mid_points, py$diff, py$salary_order))
rownames(spec_acc_salaries_plot_df) <- NULL
colnames(spec_acc_salaries_plot_df) <- c("Salary", "Diff", "Bins")
spec_acc_salaries_plot_df$Bins <- factor(spec_acc_salaries_plot_df$Bins, levels = py$salary_order)
widths <- py$widths

fig(4, 30)
g <- ggplot(data = spec_acc_salaries_plot_df, aes(x = Salary)) +
  geom_bar(aes(y = Diff), stat = 'identity', fill = green, alpha = 0.5, width = widths)
g <- g +
  basic_plot_dec() +
  basic_color_dec(green) +
  basic_orient_dec() +
  labs(title = "Salary Distribution", subtitle = "True Distribution", caption = "(Salary limited to $250K)") +
  xlab("Salary") +
  ylab("Count") +
  theme(
    axis.text.x = element_text(size = 8),
  )
print(g)


fig(4, 30)
g <- ggplot(data = spec_acc_salaries_plot_df, aes(x = Bins)) +
  geom_bar(aes(y = Diff), stat = 'identity', colour = dark_blue, fill = light_blue, alpha = 0.5)
g <- g +
  basic_plot_dec() +
  basic_color_dec(light_blue) +
  basic_orient_dec() +
  labs(title = "Salary Distribution", subtitle = "True Distribution", caption = "(Salary limited to $250K)") +
  xlab("Salary") +
  ylab("Count") +
  theme(
    axis.text.x = element_text(size = 8),
  )
print(g)


python_cmds = "
new_ppp_data['PPP_adj_sal_cats'] = new_ppp_data['final_mid'].apply(lambda x: 0 if (x * 1000000 < 5000) else 2 if (x * 1000000 > 175000) else 1)
new_ppp_data['PPP_adj_sal_cats'] = new_ppp_data['PPP_adj_sal_cats'].replace({0: '<$5000', 1: '$5000-$175000', 2: '>$175000'})
cat_avg_table = new_ppp_data.groupby('PPP_adj_sal_cats')['has_spec_acc'].aggregate([np.nanmean, 'count']).reset_index(drop = False)
cat_avg_table['nanmean'] = cat_avg_table['nanmean'].apply(lambda x: np.round(x * 1000) / 1000)
"
py_run_string(python_cmds)

g <- ggtexttable(py$cat_avg_table, rows = NULL, cols = c("Salary\nCategory", "% Having\nSpecAccs", "Count"), theme = ttheme('mBlue'))
g <- table_cell_font(g, row = 2, column = 2, face = 'bold')
g <- table_cell_font(g, row = 3, column = 2, face = 'bold')
g <- table_cell_font(g, row = 4, column = 2, face = 'bold')
print(g)

python_cmds = "
new_ppp_data['PPP_adj_sal_cats'] = new_ppp_data['PPP_adj_mid_sal'].apply(lambda x: 1 if ((x * 1000000 < 5000) | (x * 1000000 > 175000)) else 0)
val = new_ppp_data.groupby('PPP_adj_sal_cats')['has_spec_acc'].aggregate(['count', np.nanmean])
new_ppp_data_only_adj = new_ppp_data[['PPP_adj_sal_cats', 'has_spec_acc']]
new_ppp_data_only_adj['PPP_adj_sal_cats'] = new_ppp_data_only_adj['PPP_adj_sal_cats'].astype(np.int32)
new_ppp_data_only_adj['has_spec_acc'] = new_ppp_data_only_adj['has_spec_acc'].astype(np.int32)
"
py_run_string(python_cmds)
<string>:5: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
<string>:6: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
ttest <- t.test(has_spec_acc ~ PPP_adj_sal_cats, data = py$new_ppp_data_only_adj)
g <- ggparagraph(sprintf("Simple p-tests showed that SpecAccs usage for Kagglers in the extreme salary ranges was higher than for Kagglers in the non-extreme salary ranges, with the difference in usage being statistically significant (pval = %.2f)", ttest$p.value))
print(g)

python_cmds = "
age_order = [
    '18-21',
    '22-24',
    '25-29',
    '30-34',
    '35-39',
    '40-44',
    '45-49',
    '50-54',
    '55-59',
    '60-69',
    '70+',
]
ages = new_data['Q_1'].value_counts().loc[age_order]
"
py_run_string(python_cmds)
age_plot_df <- as.data.frame(list(py$age_order, py$ages))
rownames(age_plot_df) <- NULL
colnames(age_plot_df) <- c("Age", "Count")
age_plot_df$Age = factor(age_plot_df$Age, levels = py$age_order)

fig(4, 30)
g1 <- ggplot(data = age_plot_df, aes(x = Age, y = Count)) + geom_bar(stat = 'identity', colour = dark_blue, fill = light_blue, alpha = 0.6)
g1 <- g1 +
  basic_plot_dec() +
  basic_color_dec(light_blue) +
  basic_orient_dec() +
  labs(title = "Unequal Bin Widths") +
  xlab("Age Bins") +
  ylab("Count") +
  theme(
    axis.text.x = element_text(size = 8),
  )
g1

python_cmds = "
starting_points = [18, 22, 25, 30, 35, 40, 45, 50, 55, 60, 70]
widths =          [4 , 3 , 5 , 5 , 5 , 5 , 5 , 5 , 5 , 10, 10]
ending_points = np.add(starting_points, widths)
mid_points = np.add(starting_points, np.divide(widths, 2))
"
py_run_string(python_cmds)
age_plot_df <- as.data.frame(list(py$mid_points, py$ages))
rownames(age_plot_df) <- NULL
colnames(age_plot_df) <- c("Age", "Count")
widths <- py$widths

fig(4, 30)
g2 <- ggplot(data = age_plot_df, aes(x = Age, y = Count)) + geom_bar(stat = 'identity', colour = black, fill = green, alpha = 0.6, width = widths)
g2 <- g2 +
  basic_plot_dec() +
  basic_color_dec(green) +
  basic_orient_dec() +
  labs(title = "Age Distribution", subtitle = "True Distribution", caption = "(Age limited to $250K)") +
  xlab("Age") +
  ylab("Count") +
  theme(
    axis.text.x = element_text(size = 8)
  )
g2

python_cmds = "
new_age_data = new_data.copy()
new_age_data['min_age'] = (new_age_data['Q_1'].apply(lambda x: starting_points[age_order.index(x)]) - 18) / 100
new_age_data['mid_age'] = (new_age_data['Q_1'].apply(lambda x: mid_points[age_order.index(x)]) - 18) / 100
new_age_data['max_age'] = (new_age_data['Q_1'].apply(lambda x: ending_points[age_order.index(x)]) - 18) / 100
new_age_data_only_adj = new_age_data[['min_age', 'max_age']]
"
py_run_string(python_cmds)
colnames(py$new_age_data_only_adj) <- c('left', 'right')
fitted_distribution_gamma <- fitdistcens(py$new_age_data_only_adj, "gamma")

alpha <- fitted_distribution_gamma$estimate[['shape']]
beta <- fitted_distribution_gamma$estimate[['rate']]
python_cmds = "
age_bins = [[*x] for x in [*new_age_data.groupby(['min_age', 'max_age'])['kaggler_id'].aggregate('count').index]]
age_bins = pd.DataFrame(age_bins, columns = ['min', 'max'])
"
py_run_string(python_cmds)

find_actual_mid <- function(min, max, alpha, beta) {
  samples <- rgamma(1000000, shape = alpha, rate = beta)
  actual_mids <- c()
  for (idx in 1:length(min)) {
    actual_mids <- c(actual_mids, mean(samples[(samples >= min[idx]) & (samples <= max[idx])]))
  }
  actual_mids
}

py$age_bins <- py$age_bins %>%
  mutate(final_mid = find_actual_mid(min, max, alpha, beta))

python_cmds = "
new_age_data = pd.merge(new_age_data, age_bins, how = 'inner', left_on = ['min_age', 'max_age'], right_on = ['min', 'max'])
"
py_run_string(python_cmds)
python_cmds = "
new_age_ppp_data = pd.merge(new_ppp_data[['kaggler_id', 'final_mid']], new_age_data[['kaggler_id', 'final_mid']], how = 'inner', on = 'kaggler_id', suffixes = ['_salary', '_age'])
"
py_run_string(python_cmds)

#py$new_age_ppp_data$final_mid_salary <- as.numeric(py$new_age_ppp_data$final_mid_salary)
#py$new_age_ppp_data$final_mid_age <- as.numeric(py$new_age_ppp_data$final_mid_age)
g <- ggplot(data = py$new_age_ppp_data, aes(x = final_mid_salary * 1000000, y = final_mid_age * 100 + 18)) +
  stat_density2d(aes(fill = ..density..), geom = "raster", contour = FALSE) +
  scale_x_log10() +
  scale_fill_gradient(low = black, high = light_blue)
g <- g +
  basic_plot_dec() +
  basic_color_dec(light_blue) +
  basic_orient_dec() +
  labs(title = "Salary-Age Density Plot", subtitle = "Understanding the relationship between Age & Salary", caption = "(Salary is in logarithmic scale for visualization)", fill = "Density") +
  xlab("Salary") +
  ylab("Age") +
  theme(
    legend.title = element_text(colour = white, face = 'bold'),
    legend.text = element_text(colour = white),
    legend.background = element_rect(fill = alpha(light_blue, 0)),
    legend.key = element_rect(fill = alpha(light_blue, 0)),
    legend.position = c(0.95, 0.6),
    legend.key.height = unit(1.5, "cm"),
    panel.grid.major.y = element_line(color = alpha(black, 0)),
  )
g

python_cmds = "
new_age_ppp_data['final_mid_salary_cats'] = new_age_ppp_data['final_mid_salary'].apply(lambda x: 'low' if x * 1000000 < 5000 else 'high' if x * 1000000 > 175000 else 'medium')
"
py_run_string(python_cmds)

py$new_age_ppp_data$final_mid_salary_cats <- factor(py$new_age_ppp_data$final_mid_salary_cats, levels = c('low', 'medium', 'high'))
g <- ggplot(data = py$new_age_ppp_data) + geom_boxplot(aes(x = final_mid_salary_cats, y = final_mid_age * 100 + 18), colour = alpha(dark_green, 1), fill = alpha(green, 0.7), outlier.color = green, outlier.alpha = 0.01)
g <- g +
  basic_plot_dec() +
  basic_color_dec(green) +
  basic_orient_dec() +
  labs(title = "Age for different Salary Categories") +
  xlab("Salary Categories") +
  ylab("Age") +
  theme(
    axis.text.x = element_text(angle = 0, hjust = 0.5)
  )
g

py$ug_data$Age <- factor(py$ug_data$Age, levels = c("<20", "20-23", "24-29", "30-39", "40-49", ">=50"))
py$pg_data$Age <- factor(py$pg_data$Age, levels = c("<20", "20-23", "24-29", "30-39", "40-49", ">=50"))
breaks <- c(-20, 0, 20, 40)
g <- ggplot() +
  geom_bar(data = py$ug_data, aes(y = Age, x = Composition), stat = 'identity', colour = white, fill = alpha(light_blue, 0.6)) +
  geom_bar(data = py$pg_data, aes(y = Age, x = Neg_Composition), stat = 'identity', colour = white, fill = alpha(green, 0.6)) +
  geom_vline(xintercept = 0, colour = white, size = 2) +
  scale_x_continuous(breaks = breaks, labels = sapply(breaks, function(x) { abs(x) }))
g <- g +
  basic_plot_dec() +
  basic_color_dec(white) +
  basic_orient_dec(TRUE) +
  labs(title = "General Student Age Distribution", subtitle = "For undergraduate & postgraduate students") +
  xlab("Percentage") +
  ylab("Age")
g

python_cmds = "
new_ppp_cats_data = pd.merge(new_ppp_data, new_age_ppp_data, how = 'left', on = 'kaggler_id')
"
py_run_string(python_cmds)
python_cmds = "
coding_exp_order = ['< 1 years', '1-3 years', '3-5 years', '5-10 years', '10-20 years', '20+ years']
coding_exp = pd.pivot_table(new_ppp_cats_data.groupby(['final_mid_salary_cats', 'Q_4'])['kaggler_id'].aggregate('count').reset_index(drop = False), index = 'final_mid_salary_cats', columns = 'Q_4')['kaggler_id'][coding_exp_order].loc[['low', 'medium', 'high']]
coding_exp_plot_df = coding_exp / np.repeat(coding_exp.sum(axis = 1).values, repeats = 6).reshape((3, 6)) * 100
"
py_run_string(python_cmds)

py$coding_exp_plot_df <- melt(py$coding_exp_plot_df)
No id variables; using all as measure variables
py$coding_exp_plot_df$id <- c('low-salary', 'mid-salary', 'high-salary')
py$coding_exp_plot_df$id <- factor(py$coding_exp_plot_df$id, levels = c('low-salary', 'mid-salary', 'high-salary'))
py$coding_exp_plot_df$text <- apply(py$coding_exp_plot_df, 1, function(x) { paste(round(as.numeric(x['value']) * 100) / 100, "% of ", x['id'], " Kagglers have\n", x['variable'], " of coding experience", sep = "") })
g <- ggplot(data = py$coding_exp_plot_df, aes(x = id, y = variable, fill = value, text = text)) + geom_tile() + theme_ipsum()
g <- g +
  basic_plot_dec() +
  basic_color_dec(light_blue) +
  basic_orient_dec() +
  labs(title = "Coding Experience vs. Salary", subtitle = "Heatmap", fill = "Percentage") +
  xlab("Salary Category") +
  ylab("Coding Experience") +
  theme(
    legend.title = element_text(colour = white, face = 'bold'),
    legend.text = element_text(colour = white),
    legend.background = element_rect(fill = alpha(light_blue, 0)),
    legend.key = element_rect(fill = alpha(light_blue, 0)),
    legend.position = c(0.95, 0.6),
    legend.key.height = unit(1.5, "cm"),
    axis.text.x = element_text(angle = 0)
  )
g <- ggplotly(g, tooltip="text")
g
python_cmds = "
new_ppp_cats_data['Q_6'] = new_ppp_cats_data['Q_6'].map({
    '5-10 years': '5-10',
    '20 or more years': '20+',
    '3-4 years': '3-4',
    '4-5 years': '4-5',
    'I do not use machine learning methods': '<1',
    'Under 1 year': '<1',
    '2-3 years': '2-3',
    '1-2 years': '1-2',
    '10-20 years': '10-20',
})

ml_exp_order = ['<1', '1-2', '2-3', '3-4', '4-5', '5-10', '10-20', '20+']
ml_exp = pd.pivot_table(new_ppp_cats_data.groupby(['final_mid_salary_cats', 'Q_6'])['kaggler_id'].aggregate('count').reset_index(drop = False), index = 'final_mid_salary_cats', columns = 'Q_6')['kaggler_id'][ml_exp_order].loc[['low', 'medium', 'high']]
ml_exp_plot_df = ml_exp / np.repeat(ml_exp.sum(axis = 1).values, repeats = 8).reshape((3, 8)) * 100
"
py_run_string(python_cmds)

py$ml_exp_plot_df <- melt(py$ml_exp_plot_df)
No id variables; using all as measure variables
py$ml_exp_plot_df$id <- c('low-salary', 'mid-salary', 'high-salary')
py$ml_exp_plot_df$id <- factor(py$ml_exp_plot_df$id, levels = c('low-salary', 'mid-salary', 'high-salary'))
py$ml_exp_plot_df$text <- apply(py$ml_exp_plot_df, 1, function(x) { paste(round(as.numeric(x['value']) * 100) / 100, "% of ", x['id'], " Kagglers have\n", x['variable'], " of ML experience", sep = "") })
g <- ggplot(data = py$ml_exp_plot_df, aes(x = id, y = variable, fill = value, text = text)) + geom_tile() + theme_ipsum()
g <- g +
  basic_plot_dec() +
  basic_color_dec(light_blue) +
  basic_orient_dec() +
  labs(title = "ML Experience vs. Salary", subtitle = "Heatmap", fill = "Percentage") +
  xlab("Salary Category") +
  ylab("ML Experience") +
  theme(
    legend.title = element_text(colour = white, face = 'bold'),
    legend.text = element_text(colour = white),
    legend.background = element_rect(fill = alpha(light_blue, 0)),
    legend.key = element_rect(fill = alpha(light_blue, 0)),
    legend.position = c(0.95, 0.6),
    legend.key.height = unit(1.5, "cm"),
    axis.text.x = element_text(angle = 0)
  )
g <- ggplotly(g, tooltip="text")
g
python_cmds = "
new_ppp_cats_data['Q_11'] = new_ppp_cats_data['Q_11'].map({
    '$100-$999': '100-999',
    '$10,000-$99,999': '10000-99999',
    '$1000-$9,999': '1000-9999',
    '$0 ($USD)': '0',
    '$1-$99': '1-99',
    '$100,000 or more ($USD)': '>100000'
})
money_spent_order = ['0', '1-99', '100-999', '1000-9999', '10000-99999', '>100000']
money_spent = pd.pivot_table(new_ppp_cats_data.groupby(['final_mid_salary_cats', 'Q_11'])['kaggler_id'].aggregate('count').reset_index(drop = False), index = 'final_mid_salary_cats', columns = 'Q_11')['kaggler_id'][money_spent_order].loc[['low', 'medium', 'high']]
money_spent_plot_df = money_spent / np.repeat(money_spent.sum(axis = 1).values, repeats = 6).reshape((3, 6)) * 100
"
py_run_string(python_cmds)

py$money_spent_plot_df <- melt(py$money_spent_plot_df)
No id variables; using all as measure variables
py$money_spent_plot_df$id <- c('low-salary', 'mid-salary', 'high-salary')
py$money_spent_plot_df$id <- factor(py$money_spent_plot_df$id, levels = c('low-salary', 'mid-salary', 'high-salary'))
py$money_spent_plot_df$text <- apply(py$money_spent_plot_df, 1, function(x) { paste(round(as.numeric(x['value']) * 100) / 100, "% of ", x['id'], " Kagglers have\nspent $", x['variable'], " on ML services", sep = "") })
g <- ggplot(data = py$money_spent_plot_df, aes(x = id, y = variable, fill = value, text = text)) + geom_tile() + theme_ipsum()
g <- g +
  basic_plot_dec() +
  basic_color_dec(light_blue) +
  basic_orient_dec() +
  labs(title = "Money Spent on ML services vs. Salary", subtitle = "Heatmap", fill = "Percentage") +
  xlab("Salary Category") +
  ylab("Money Spent") +
  theme(
    legend.title = element_text(colour = white, face = 'bold'),
    legend.text = element_text(colour = white),
    legend.background = element_rect(fill = alpha(light_blue, 0)),
    legend.key = element_rect(fill = alpha(light_blue, 0)),
    legend.position = c(0.95, 0.6),
    legend.key.height = unit(1.5, "cm"),
    axis.text.x = element_text(angle = 0),
    plot.subtitle = element_text(colour = white)
  )
g <- ggplotly(g, tooltip="text")
g
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKPGRpdiBhbGlnbj0nY2VudGVyJyBzdHlsZT0iYmFjZ3JvdW5kLWNvbG9yOiAnYmx1ZSc7Ij4KICAgIDxmb250IHNpemU9JyszJyBjb2xvcj0nI2RkMDAwMCc+CiAgICAgICAgPGI+VGhlIFNwZWNBY2NzIE11cmRlciBDb25zcGlyYWN5ITwvYj4KICAgIDwvZm9udD4KICAgIDxicj4KICAgIDxmb250IHNpemU9JysyLjUnIGNvbG9yPScjRkJBQjYwJz4KICAgICAgICA8Yj5BIFNob3J0IERhdGEgQW5hbHl0aWMgQ29taWM8L2I+CiAgICA8L2ZvbnQ+CjwvZGl2PgoKYGBge3J9CiMgSW1wb3J0aW5nIFIgbGlicmFyaWVzCgpsaWJyYXJ5KHJldGljdWxhdGUpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KG11bHRpZHBseXIpCmxpYnJhcnkoZml0ZGlzdHJwbHVzKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KGNsaXByKQpsaWJyYXJ5KHNjYWxlcykKI2luc3RhbGwucGFja2FnZXMoImdncHViciIpCmxpYnJhcnkoZ2dwdWJyKQpsaWJyYXJ5KGhyYnJ0aGVtZXMpCmxpYnJhcnkocGxvdGx5KQpsaWJyYXJ5KHJlc2hhcGUyKQpsaWJyYXJ5KGRldnRvb2xzKQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImVsbGlzcC9mcnMtci1wYWNrYWdlL3BrZyIpCmxpYnJhcnkoZnJzKQpgYGAKCmBgYHtyfQpweXRob25fY21kcyA8LSAiCiMgSW1wb3J0aW5nIGRhdGEgcHJvY2Vzc2luZyBsaWJyYXJpZXMKaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBwYW5kYXMgYXMgcGQKIgpweV9ydW5fc3RyaW5nKHB5dGhvbl9jbWRzKQpgYGAKCmBgYHtyfQpweXRob25fY21kcyA8LSAiCiMgTG9hZGluZyBkYXRhc2V0cwojICAgMS4gS2FnZ2xlIE1MICYgREwgU3VydmV5IDIwMjEKIyAgIDIuIEJpZyBNYWMgSW5kZXgKIyAgIDMuIEdyYWR1YXRlIEFnZSBEYXRhc2V0CiMgICA0LiBHUFUgUHJpY2luZyBEYXRhc2V0CgojIEthZ2dsZSBNTCAmIERMIFN1cnZleSAyMDIxIERhdGEKIyAgIEluZm86IDIwMjEKIyAgIExpbms6IGh0dHBzOi8vd3d3LmthZ2dsZS5jb20vYy9rYWdnbGUtc3VydmV5LTIwMjEvZGF0YQpkZWYgZGF0YV9wcm9jZXNzaW5nX2Z1bmMoZGF0YSk6CiAgICBjb2xzID0gWypkYXRhLmNvbHVtbnNdCiAgICBncm91cF9jb2xzID0gW3ggZm9yIHggaW4gY29scyBpZiAnU2VsZWN0ZWQgQ2hvaWNlJyBpbiB4XQogICAgZ3JvdXBfY29sc19tYWluX3F1ZXMgPSBbeC5zcGxpdCgnIC0gU2VsZWN0ZWQgQ2hvaWNlJylbMF0gZm9yIHggaW4gZ3JvdXBfY29sc10KICAgIGdyb3VwX2NvbHNfdW5pcV9tYWluX3F1ZXMgPSBucC51bmlxdWUoZ3JvdXBfY29sc19tYWluX3F1ZXMpCiAgICAKICAgIGRlZiBncm91cF9jb2xzX2Z1bmMoeCk6CiAgICAgICAgYW5zd2VyX2xpc3QgPSBbXQogICAgICAgIGZvciBjb2wgaW4geC5rZXlzKCk6CiAgICAgICAgICAgIGlmKHBkLmlzbnVsbCh4W2NvbF0pID09IEZhbHNlKToKICAgICAgICAgICAgICAgIGFuc3dlcl9saXN0LmFwcGVuZCh4W2NvbF0pCiAgICAgICAgcmV0dXJuICcgfCAnLmpvaW4oYW5zd2VyX2xpc3QpCgogICAgZ3JvdXBlZF9jb2xzX2RmID0gcGQuRGF0YUZyYW1lKCkKICAgIGZvciBxdWVzX2lkeCwgcXVlcyBpbiBlbnVtZXJhdGUoZ3JvdXBfY29sc191bmlxX21haW5fcXVlcyk6CiAgICAgICAgcXVlc19ncm91cF9jb2xzID0gW3ggZm9yIHggaW4gZ3JvdXBfY29scyBpZiB4LnN0YXJ0c3dpdGgocXVlcyldCiAgICAgICAgZ3JvdXBlZF9jb2xfZGYgPSBkYXRhW3F1ZXNfZ3JvdXBfY29sc10uYXBwbHkoZ3JvdXBfY29sc19mdW5jLCBheGlzID0gMSkKICAgICAgICBncm91cGVkX2NvbHNfZGYgPSBwZC5jb25jYXQoW2dyb3VwZWRfY29sc19kZiwgZ3JvdXBlZF9jb2xfZGZdLCBheGlzID0gMSkKICAgIGdyb3VwZWRfY29sc19kZi5jb2x1bW5zID0gZ3JvdXBfY29sc191bmlxX21haW5fcXVlcwogICAgCiAgICBkYXRhID0gZGF0YS5kcm9wKGdyb3VwX2NvbHMsIGF4aXMgPSAxKQogICAgZGF0YSA9IHBkLmNvbmNhdChbZGF0YSwgZ3JvdXBlZF9jb2xzX2RmXSwgYXhpcyA9IDEpCiAgICByZXR1cm4gZGF0YQpyYXdfZGF0YSA9IHBkLnJlYWRfY3N2KCcuLi9EYXRhL2thZ2dsZS1zdXJ2ZXktMjAyMS9rYWdnbGVfc3VydmV5XzIwMjFfcmVzcG9uc2VzLmNzdicsIGhlYWRlciA9IFswXSwgc2tpcHJvd3MgPSBbMF0pCmRhdGEgPSBkYXRhX3Byb2Nlc3NpbmdfZnVuYyhyYXdfZGF0YSkKZGF0YV9xdWVzdGlvbnMgPSBkYXRhLmNvbHVtbnMKZGF0YS5jb2x1bW5zID0gW2YnUV97eH0nIGZvciB4IGluIHJhbmdlKGRhdGEuc2hhcGVbMV0pXQpyYXdfZGF0YVsna2FnZ2xlcl9pZCddID0gcGQuU2VyaWVzKG5wLmFyYW5nZShyYXdfZGF0YS5zaGFwZVswXSkpCmRhdGFbJ2thZ2dsZXJfaWQnXSA9IHBkLlNlcmllcyhucC5hcmFuZ2UoZGF0YS5zaGFwZVswXSkpCgojIEJpZyBNYWMgSW5kZXgKIyAgIEluZm86IEdEUC1hZGp1c3RlZCwgSnVsIDIwMjEKIyAgIExpbms6IGh0dHBzOi8vd3d3LmVjb25vbWlzdC5jb20vYmlnLW1hYy1pbmRleApiaWdtYWNfaWR4ID0gewogICAgJ0luZGlhJzogLTE4LjQsCiAgICAnSW5kb25lc2lhJzogLTI2LjgsCiAgICAnUGFraXN0YW4nOiAxNi41LAogICAgJ01leGljbyc6IC02LjEsCiAgICAnUnVzc2lhJzogLTM0LjMsCiAgICAnVHVya2V5JzogLTMxLjEsCiAgICAnQXVzdHJhbGlhJzogLTguMSwKICAgICdOaWdlcmlhJzogbnAubmFuLAogICAgJ0dyZWVjZSc6IDkuMiwKICAgICdCZWxnaXVtJzogOS4yLAogICAgJ0phcGFuJzogLTI0LjQsCiAgICAnRWd5cHQnOiAtMTQuOSwKICAgICdTaW5nYXBvcmUnOiAtMjEuMSwKICAgICdCcmF6aWwnOiAzMS41LAogICAgJ1BvbGFuZCc6IC02LjcsCiAgICAnQ2hpbmEnOiAtMC40LAogICAgJ0lyYW4sIElzbGFtaWMgUmVwdWJsaWMgb2YuLi4nOiBucC5uYW4sCiAgICAnVW5pdGVkIFN0YXRlcyBvZiBBbWVyaWNhJzogMCwKICAgICdJdGFseSc6IDkuMiwKICAgICdWaWV0IE5hbSc6IC01LjgsCiAgICAnSXNyYWVsJzogNi43LAogICAgJ1BlcnUnOiAtMC43LAogICAgJ1NvdXRoIEFmcmljYSc6IC0yOS42LAogICAgJ090aGVyJzogbnAubmFuLAogICAgJ1NwYWluJzogOS4yLAogICAgJ0JhbmdsYWRlc2gnOiBucC5uYW4sCiAgICAnVW5pdGVkIEtpbmdkb20gb2YgR3JlYXQgQnJpdGFpbiBhbmQgTm9ydGhlcm4gSXJlbGFuZCc6IDEuMCwKICAgICdGcmFuY2UnOiA5LjIsCiAgICAnU3dpdHplcmxhbmQnOiA2LjUsCiAgICAnQWxnZXJpYSc6IG5wLm5hbiwKICAgICdUdW5pc2lhJzogbnAubmFuLAogICAgJ0FyZ2VudGluYSc6IDE2LjMsCiAgICAnU3dlZGVuJzogMTkuOCwKICAgICdDb2xvbWJpYSc6IDMuNSwKICAgICdJIGRvIG5vdCB3aXNoIHRvIGRpc2Nsb3NlIG15IGxvY2F0aW9uJzogbnAubmFuLAogICAgJ0NhbmFkYSc6IDEwLjIsCiAgICAnQ2hpbGUnOiAxMC4yLAogICAgJ05ldGhlcmxhbmRzJzogOS4yLAogICAgJ1VrcmFpbmUnOiAtMjUuMSwKICAgICdTYXVkaSBBcmFiaWEnOiAtMy41LAogICAgJ1JvbWFuaWEnOiAtMjkuMCwKICAgICdNb3JvY2NvJzogbnAubmFuLAogICAgJ0F1c3RyaWEnOiA5LjIsCiAgICAnVGFpd2FuJzogLTM4LjksCiAgICAnS2VueWEnOiBucC5uYW4sCiAgICAnQmVsYXJ1cyc6IG5wLm5hbiwKICAgICdJcmVsYW5kJzogOS4yLAogICAgJ1BvcnR1Z2FsJzogOS4yLAogICAgJ0hvbmcgS29uZyAoUy5BLlIuKSc6IC00NS42LAogICAgJ0Rlbm1hcmsnOiAtMTQuMiwKICAgICdHZXJtYW55JzogOS4yLAogICAgJ1NvdXRoIEtvcmVhJzogLTcuOCwKICAgICdQaGlsaXBwaW5lcyc6IC0xMS4yLAogICAgJ1NyaSBMYW5rYSc6IDEwLjAsCiAgICAnVW5pdGVkIEFyYWIgRW1pcmF0ZXMnOiAtNy44LAogICAgJ1VnYW5kYSc6IG5wLm5hbiwKICAgICdHaGFuYSc6IG5wLm5hbiwKICAgICdNYWxheXNpYSc6IC0zMS43LAogICAgJ1RoYWlsYW5kJzogMTcuMCwKICAgICdOZXBhbCc6IG5wLm5hbiwKICAgICdLYXpha2hzdGFuJzogbnAubmFuLAogICAgJ0V0aGlvcGlhJzogbnAubmFuLAogICAgJ0lyYXEnOiBucC5uYW4sCiAgICAnRWN1YWRvcic6IG5wLm5hbiwKICAgICdOb3J3YXknOiA4LjYsCiAgICAnQ3plY2ggUmVwdWJsaWMnOiAyLjksCn0KCiMgR3JhZHVhdGUgQWdlIERhdGFzZXQKIyAgIEluZm86IDIwMjAtMjEsIFVHICYgUEcgc3R1ZGVudHMKIyAgIExpbms6IGh0dHBzOi8vaGVhLmllL3N0YXRpc3RpY3MvZGF0YS1mb3ItZG93bmxvYWQtYW5kLXZpc3VhbGlzYXRpb25zL2Vucm9sbWVudHMva2V5LWZhY3RzLWZpZ3VyZXMtMjAyMC0yMDIxLwpkZWYgZ2V0X3N0dWRlbnRfYWdlX2RhdGEoKToKICAgIGRhdGEgPSBwZC5yZWFkX2NzdignLi4vRGF0YS9zdHVkZW50X2FnZS5jc3YnKQogICAgdWdfZGF0YSA9IGRhdGFbZGF0YVsnQ291cnNlJ10gPT0gJ1VHJ10KICAgIHBnX2RhdGEgPSBkYXRhW2RhdGFbJ0NvdXJzZSddID09ICdQRyddCiAgICBwZ19kYXRhID0gcGdfZGF0YS5hc3NpZ24oTmVnX0NvbXBvc2l0aW9uID0gLXBnX2RhdGFbJ0NvbXBvc2l0aW9uJ10pCiAgICByZXR1cm4gdWdfZGF0YSwgcGdfZGF0YQp1Z19kYXRhLCBwZ19kYXRhID0gZ2V0X3N0dWRlbnRfYWdlX2RhdGEoKQoKIyBHUFUgUHJpY2luZyBEYXRhc2V0CiMgICBJbmZvOiAyMDE4CiMgICBMaW5rOiBodHRwczovL3d3dy5rYWdnbGUuY29tL3JhY3plcS9ldGhlcmV1bS1lZmZlY3QtcGMtcGFydHMKZGVmIGdldF9ncHVfcHJpY2VzKCk6CiAgICBkYXRhID0gcGQucmVhZF9jc3YoJy4uL0RhdGEvZ3B1X3ByaWNlcy9GQUNUX0dQVV9QUklDRS5jc3YnKQogICAgdGltZV9kYXRhID0gcGQucmVhZF9jc3YoJy4uL0RhdGEvZ3B1X3ByaWNlcy9ESU1fVElNRS5jc3YnKQogICAgcHJvZF9kYXRhID0gcGQucmVhZF9jc3YoJy4uL0RhdGEvZ3B1X3ByaWNlcy9ESU1fR1BVX1BST0QuY3N2JykKICAgIHJlZ2lvbl9kYXRhID0gcGQucmVhZF9jc3YoJy4uL0RhdGEvZ3B1X3ByaWNlcy9ESU1fUkVHSU9OLmNzdicpCiAgICBwcm9kX2RhdGFbJ01lbW9yeV9DYXBhY2l0eV9jYXRlZ29yeSddID0gcHJvZF9kYXRhWydNZW1vcnlfQ2FwYWNpdHknXS5hcHBseShsYW1iZGEgeDogJ2hpZ2gnIGlmIHggPj0gMTAgZWxzZSAnbWVkaXVtJyBpZiB4ID49MiBlbHNlICdsb3cnKQogICAgbWVyZ2VkX2RhdGEgPSBwZC5tZXJnZShkYXRhLCB0aW1lX2RhdGEsIGhvdyA9ICdsZWZ0JywgbGVmdF9vbiA9ICdUaW1lSWQnLCByaWdodF9vbiA9ICdJZCcpCiAgICBtZXJnZWRfZGF0YSA9IHBkLm1lcmdlKG1lcmdlZF9kYXRhLCBwcm9kX2RhdGEsIGhvdyA9ICdsZWZ0JywgbGVmdF9vbiA9ICdQcm9kSWQnLCByaWdodF9vbiA9ICdJZCcpCiAgICBtZXJnZWRfZGF0YSA9IHBkLm1lcmdlKG1lcmdlZF9kYXRhLCByZWdpb25fZGF0YSwgaG93ID0gJ2xlZnQnLCBsZWZ0X29uID0gJ1JlZ2lvbklkJywgcmlnaHRfb24gPSAnSWQnKQoKICAgIGNvdW50cnlfY29kZV9uYW1lX21hcCA9IHsKICAgICAgICAnYXUnOiAnQXVzdHJhbGlhJywKICAgICAgICAnYmUnOiAnQmVsZ2l1bScsCiAgICAgICAgJ2NhJzogJ0NhbmFkYScsCiAgICAgICAgJ2RlJzogJ0dlcm1hbnknLAogICAgICAgICdlcyc6ICdTcGFpbicsCiAgICAgICAgJ2ZyJzogJ0ZyYW5jZScsCiAgICAgICAgJ2llJzogJ0lyZWxhbmQnLAogICAgICAgICdpdCc6ICdJdGFseScsCiAgICAgICAgJ256JzogJ05ldyBaZWFsYW5kJywKICAgICAgICAndWsnOiAnVW5pdGVkIEtpbmdkb20gb2YgR3JlYXQgQnJpdGFpbiBhbmQgTm9ydGhlcm4gSXJlbGFuZCcsCiAgICAgICAgJ3VzJzogJ1VuaXRlZCBTdGF0ZXMgb2YgQW1lcmljYScKICAgIH0KICAgIG1lcmdlZF9kYXRhWydDb2RlJ10gPSBtZXJnZWRfZGF0YVsnQ29kZSddLm1hcChjb3VudHJ5X2NvZGVfbmFtZV9tYXApCiAgICBtZXJnZWRfZGF0YSA9IG1lcmdlZF9kYXRhW21lcmdlZF9kYXRhWydDb2RlJ10gIT0gJ05ldyBaZWFsYW5kJ10KICAgIG1lcmdlZF9kYXRhWydCaWdNYWNfaW5kZXgnXSA9IG1lcmdlZF9kYXRhWydDb2RlJ10ubWFwKGJpZ21hY19pZHgpCiAgICBtZXJnZWRfZGF0YVsncHBwX3ByaWNlJ10gPSBtZXJnZWRfZGF0YVsnUHJpY2VfVVNEJ10gKiAoMSArIChtZXJnZWRfZGF0YVsnQmlnTWFjX2luZGV4J10gLyAxMDAuMCkpCiAgICByZXR1cm4gbWVyZ2VkX2RhdGEKZ3B1X3ByaWNlc19kYXRhID0gZ2V0X2dwdV9wcmljZXMoKQpncHVfcHJpY2VzX2RhdGEgPSBncHVfcHJpY2VzX2RhdGFbZ3B1X3ByaWNlc19kYXRhWydZZWFyJ10gPT0gMjAxOF0KIgpweV9ydW5fc3RyaW5nKHB5dGhvbl9jbWRzKQpgYGAKCmBgYHtyfQojIEdyYXBoaW5nIFV0aWxzCgpibGFjayA8LSAnIzI0MDExNScKbGlnaHRfYmx1ZSA8LSAnIzRjYmVmZCcKZGFya19ibHVlIDwtICcjMTE0YjVmJwpwdXJwbGUgPC0gJyM2NDU3YTYnCmdyZWVuIDwtICcjOTlmZmQxJwpkYXJrX2dyZWVuIDwtICIjM0U4ODVCIgpyZWQgPC0gJyNkZDAwMDAnCndoaXRlIDwtICcjZmFlMWRmJwoKYmFzaWNfcGxvdF9kZWMgPC0gZnVuY3Rpb24oKSB7CiAgdGhlbWUoCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IGJsYWNrKSwKICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IGJsYWNrKSwKICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2xpbmUoY29sb3IgPSBhbHBoYShibGFjaywgMCkpLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvciA9IHdoaXRlKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3IgPSB3aGl0ZSksCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X3RleHQoY29sb3IgPSB3aGl0ZSksCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoY29sb3IgPSB3aGl0ZSksCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoY29sb3IgPSB3aGl0ZSksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChjb2xvciA9IGFscGhhKHdoaXRlLCAwLjc1KSksCiAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChjb2xvciA9IGFscGhhKHdoaXRlLCAwLjc1KSkKICApCn0KCmJhc2ljX2NvbG9yX2RlYyA8LSBmdW5jdGlvbihjb2xvcikgewogIHRoZW1lKAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvciA9IGFscGhhKGNvbG9yLCAwLjc1KSkKICApCn0KCmJhc2ljX29yaWVudF9kZWMgPC0gZnVuY3Rpb24oaW52KSB7CiAgaWYobWlzc2luZyhpbnYpKSBpbnYgPC0gRkFMU0UKICBpZihpbnYpIHsKICAgIHRoZW1lKAogICAgICBwYW5lbC5ncmlkLm1ham9yLnkgPSBlbGVtZW50X2xpbmUoY29sb3IgPSBhbHBoYShibGFjaywgMCkpLAogICAgICBwYW5lbC5ncmlkLm1ham9yLnggPSBlbGVtZW50X2xpbmUoY29sb3IgPSBhbHBoYSh3aGl0ZSwgMC4yNSkpLAogICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDEpCiAgICApCiAgfQogIGVsc2UgewogICAgdGhlbWUoCiAgICAgIHBhbmVsLmdyaWQubWFqb3IueCA9IGVsZW1lbnRfbGluZShjb2xvciA9IGFscGhhKGJsYWNrLCAwKSksCiAgICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvciA9IGFscGhhKHdoaXRlLCAwLjI1KSksCiAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkKICAgICkKICB9Cn0KCmZpZyA8LSBmdW5jdGlvbih3aWR0aCwgaGVpZ3RoKXsKICAgICBvcHRpb25zKHJlcHIucGxvdC53aWR0aCA9IHdpZHRoLCByZXByLnBsb3QuaGVpZ2h0ID0gaGVpZ3RoKQp9CmBgYAoKCmBgYHtyfQpweXRob25fY21kcyA8LSAiCmRlZiBwbG90X3VuaXFfdmFsc19pbl9xdWVzKHF1ZXMpOgogICAgZGF0YSA9IHJhd19kYXRhLnNldF9pbmRleCgna2FnZ2xlcl9pZCcpCiAgICBzZWxfY29scyA9IFt4IGZvciB4IGluIGRhdGEuY29sdW1ucyBpZiBxdWVzIGluIHhdCiAgICB2YWxfY250cyA9IChkYXRhLnNoYXBlWzBdIC0gcGQuaXNudWxsKGRhdGFbc2VsX2NvbHNdKS5zdW0oKS5zb3J0X2luZGV4KCkpIC8gZGF0YS5zaGFwZVswXSAqIDEwMAogICAgdmFsX2NudHNfeCA9IFt4LnNwbGl0KCdTZWxlY3RlZCBDaG9pY2UgLSAnKVstMV0gZm9yIHggaW4gdmFsX2NudHMuaW5kZXhdCiAgICB2YWxfY250c195ID0gdmFsX2NudHMKICAgIHJldHVybiB2YWxfY250c194LCB2YWxfY250c195CnZhbF94LCB2YWxfeSA9IHBsb3RfdW5pcV92YWxzX2luX3F1ZXMoJ1doaWNoIHR5cGVzIG9mIHNwZWNpYWxpemVkIGhhcmR3YXJlIGRvIHlvdSB1c2Ugb24gYSByZWd1bGFyIGJhc2lzPycpCiIKcHlfcnVuX3N0cmluZyhweXRob25fY21kcykKCnZhbF9wbG90X2RmIDwtIGFzLmRhdGEuZnJhbWUobGlzdChweSR2YWxfeCwgcHkkdmFsX3kpKQpyb3duYW1lcyh2YWxfcGxvdF9kZikgPC0gTlVMTApjb2xuYW1lcyh2YWxfcGxvdF9kZikgPC0gYygnTmFtZScsICdQZXJjZW50JykKCmcgPC0gZ2dwbG90KGRhdGEgPSB2YWxfcGxvdF9kZiwgYWVzKHkgPSBOYW1lLCB4ID0gUGVyY2VudCkpICsgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIGNvbG91ciA9IGRhcmtfYmx1ZSwgZmlsbCA9IGxpZ2h0X2JsdWUsIGFscGhhID0gMC42KQpnIDwtIGcgKyAKICBiYXNpY19wbG90X2RlYygpICsgCiAgYmFzaWNfY29sb3JfZGVjKGxpZ2h0X2JsdWUpICsgCiAgYmFzaWNfb3JpZW50X2RlYyhUUlVFKSArIAogIGdndGl0bGUoIlVzYWdlIG9mIFNwZWNpYWxpemVkIEFjY2VsZXJhdG9ycyIpICsKICB4bGFiKCJQZXJjZW50YWdlIG9mIEthZ2dsZXIgdXNpbmcgU3BlYyBBY2MgKCUpIikgKwogIHlsYWIoIlNwZWMgQWNjIFR5cGUiKQpnCmBgYAoKYGBge3J9CnB5dGhvbl9jbWRzIDwtICIKIyBUT0RPIGluIFIKIyBQcmludCBkZXRhaWxzIGFib3V0IHNpemUgb2YgcHJvY2Vzc2VkIGRhdGFzZXQgJiBudW1iZXIgb2YgZXhjbHVkZWQgY29sdW1ucwpzZWxfY29scyA9IFt4IGZvciB4IGluIHJhd19kYXRhLmNvbHVtbnMgaWYgJ1doaWNoIHR5cGVzIG9mIHNwZWNpYWxpemVkIGhhcmR3YXJlIGRvIHlvdSB1c2Ugb24gYSByZWd1bGFyIGJhc2lzPycgaW4geF0KZXhjbF9pbmRleCA9IHBkLmlzbnVsbChyYXdfZGF0YVtzZWxfY29sc10pLnN1bShheGlzID0gMSkKZXhjbF9pbmRleCA9IGV4Y2xfaW5kZXhbZXhjbF9pbmRleCA9PSA2XS5pbmRleApwcmludChmJ05vLiBvZiBFeGNsdWRlZCBLYWdnbGVyczoge2xlbihleGNsX2luZGV4KX0nKQpuZXdfZGF0YSA9IGRhdGEuZHJvcChleGNsX2luZGV4LCBheGlzID0gMCkKcHJpbnQoZidTaXplIG9mIHByb2Nlc3NlZCBkYXRhOiB7bmV3X2RhdGEuc2hhcGV9JykKbmV3X2RhdGFbJ2hhc19zcGVjX2FjYyddID0gbmV3X2RhdGFbJ1FfNTAnXS5hcHBseShsYW1iZGEgeDogeCAhPSAnTm9uZScpCm5ld19kYXRhWydudW1fc3BlY19hY2MnXSA9IG5ld19kYXRhWydRXzUwJ10uYXBwbHkobGFtYmRhIHg6IDAgaWYgeCA9PSAnTm9uZScgZWxzZSBsZW4oeC5zcGxpdCgnfCcpKSkKIgpweV9ydW5fc3RyaW5nKHB5dGhvbl9jbWRzKQpgYGAKCmBgYHtyfQpweXRob25fY21kcyA8LSAiCnNhbGFyeV9vcmRlciA9IFsKICAgICckMC05OTknLAogICAgJzEsMDAwLTEsOTk5JywKICAgICcyLDAwMC0yLDk5OScsCiAgICAnMywwMDAtMyw5OTknLAogICAgJzQsMDAwLTQsOTk5JywKICAgICc1LDAwMC03LDQ5OScsCiAgICAnNyw1MDAtOSw5OTknLAogICAgJzEwLDAwMC0xNCw5OTknLAogICAgJzE1LDAwMC0xOSw5OTknLAogICAgJzIwLDAwMC0yNCw5OTknLAogICAgJzI1LDAwMC0yOSw5OTknLAogICAgJzMwLDAwMC0zOSw5OTknLAogICAgJzQwLDAwMC00OSw5OTknLAogICAgJzUwLDAwMC01OSw5OTknLAogICAgJzYwLDAwMC02OSw5OTknLAogICAgJzcwLDAwMC03OSw5OTknLAogICAgJzgwLDAwMC04OSw5OTknLAogICAgJzkwLDAwMC05OSw5OTknLAogICAgJzEwMCwwMDAtMTI0LDk5OScsCiAgICAnMTI1LDAwMC0xNDksOTk5JywKICAgICcxNTAsMDAwLTE5OSw5OTknLAogICAgJzIwMCwwMDAtMjQ5LDk5OScsCiAgICAnMjUwLDAwMC0yOTksOTk5JywKICAgICczMDAsMDAwLTQ5OSw5OTknLAogICAgJyQ1MDAsMDAwLTk5OSw5OTknLAogICAgJz4kMSwwMDAsMDAwJwpdCnNhbGFyaWVzID0gbmV3X2RhdGFbJ1FfMTAnXS52YWx1ZV9jb3VudHMoKS5sb2Nbc2FsYXJ5X29yZGVyXQoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCmBgYAoKCmBgYHtyfQpzYWxhcnlfcGxvdF9kZiA8LSBhcy5kYXRhLmZyYW1lKGxpc3QocHkkc2FsYXJ5X29yZGVyLCBweSRzYWxhcmllcykpCnJvd25hbWVzKHNhbGFyeV9wbG90X2RmKSA8LSBOVUxMCmNvbG5hbWVzKHNhbGFyeV9wbG90X2RmKSA8LSBjKCJTYWxhcnkiLCAiQ291bnQiKQpzYWxhcnlfcGxvdF9kZiRTYWxhcnkgPSBmYWN0b3Ioc2FsYXJ5X3Bsb3RfZGYkU2FsYXJ5LCBsZXZlbHMgPSBweSRzYWxhcnlfb3JkZXIpCgpmaWcoNCwgMzApCmcxIDwtIGdncGxvdChkYXRhID0gc2FsYXJ5X3Bsb3RfZGYsIGFlcyh4ID0gU2FsYXJ5LCB5ID0gQ291bnQpKSArIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknLCBjb2xvdXIgPSBkYXJrX2JsdWUsIGZpbGwgPSBsaWdodF9ibHVlLCBhbHBoYSA9IDAuNikKZzEgPC0gZzEgKwogIGJhc2ljX3Bsb3RfZGVjKCkgKwogIGJhc2ljX2NvbG9yX2RlYyhsaWdodF9ibHVlKSArCiAgYmFzaWNfb3JpZW50X2RlYygpICsKICBsYWJzKHRpdGxlID0gIlVuZXF1YWwgQmluIFdpZHRocyIpICsKICB4bGFiKCJTYWxhcnkgQmlucyIpICsKICB5bGFiKCJDb3VudCIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KQogICkKZzEKYGBgCgpgYGB7cn0KcHl0aG9uX2NtZHMgPC0gIgpzdGFydGluZ19wb2ludHMgPSBbMCAgICwgMTAwMCwgMjAwMCwgMzAwMCwgNDAwMCwgNTAwMCwgNzUwMCwgMTAwMDAsIDE1MDAwLCAyMDAwMCwgMjUwMDAsIDMwMDAwLCA0MDAwMCwgNTAwMDAsIDYwMDAwLCA3MDAwMCwgODAwMDAsIDkwMDAwLCAxMDAwMDAsIDEyNTAwMCwgMTUwMDAwLCAyMDAwMDAsIDI1MDAwMCwgMzAwMDAwLCA1MDAwMDAsIDEwMDAwMDBdCndpZHRocyA9ICAgICAgICAgIFsxMDAwLCAxMDAwLCAxMDAwLCAxMDAwLCAxMDAwLCAyNTAwLCAyNTAwLCAgNTAwMCwgIDUwMDAsICA1MDAwLCAgNTAwMCwgMTAwMDAsIDEwMDAwLCAxMDAwMCwgMTAwMDAsIDEwMDAwLCAxMDAwMCwgMTAwMDAsICAyNTAwMCwgIDI1MDAwLCAgNTAwMDAsICA1MDAwMCwgIDUwMDAwLCAyMDAwMDAsIDUwMDAwMCwgMTAwMDAwMF0KZW5kaW5nX3BvaW50cyA9IG5wLmFkZChzdGFydGluZ19wb2ludHMsIHdpZHRocykKbWlkX3BvaW50cyA9IG5wLmFkZChzdGFydGluZ19wb2ludHMsIG5wLmRpdmlkZSh3aWR0aHMsIDIpKQoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCmBgYAoKYGBge3J9CnNhbGFyeV9wbG90X2RmIDwtIGFzLmRhdGEuZnJhbWUobGlzdChweSRtaWRfcG9pbnRzLCBweSRzYWxhcmllcykpCnJvd25hbWVzKHNhbGFyeV9wbG90X2RmKSA8LSBOVUxMCmNvbG5hbWVzKHNhbGFyeV9wbG90X2RmKSA8LSBjKCJTYWxhcnkiLCAiQ291bnQiKQp3aWR0aHMgPC0gcHkkd2lkdGhzCgpmaWcoNCwgMzApCmcyIDwtIGdncGxvdChkYXRhID0gc2FsYXJ5X3Bsb3RfZGYsIGFlcyh4ID0gU2FsYXJ5LCB5ID0gQ291bnQpKSArIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknLCBjb2xvdXIgPSBibGFjaywgZmlsbCA9IGdyZWVuLCBhbHBoYSA9IDAuNiwgd2lkdGggPSB3aWR0aHMpCmcyIDwtIGcyICsKICBiYXNpY19wbG90X2RlYygpICsKICBiYXNpY19jb2xvcl9kZWMoZ3JlZW4pICsKICBiYXNpY19vcmllbnRfZGVjKCkgKwogIGxhYnModGl0bGUgPSAiU2FsYXJ5IERpc3RyaWJ1dGlvbiIsIHN1YnRpdGxlID0gIlRydWUgRGlzdHJpYnV0aW9uIiwgY2FwdGlvbiA9ICIoU2FsYXJ5IGxpbWl0ZWQgdG8gJDI1MEspIikgKwogIHhsYWIoIlNhbGFyeSIpICsKICB5bGFiKCJDb3VudCIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSwKICAgIGFzcGVjdC5yYXRpbyA9IDQvMTUKICApICsKICB4bGltKDAsIDI1MDAwMCkKZzIKYGBgCgpgYGB7cn0KcHl0aG9uX2NtZHMgPC0gIgpuZXdfcHBwX2RhdGEgPSBuZXdfZGF0YS5jb3B5KCkKbmV3X3BwcF9kYXRhWydQUFBfYWRqdXN0ZWQnXSA9IG5ld19kYXRhWydRXzInXS5tYXAoYmlnbWFjX2lkeCkKbmV3X3BwcF9kYXRhID0gbmV3X3BwcF9kYXRhLmxvY1t+cGQuaXNudWxsKG5ld19wcHBfZGF0YVsnUFBQX2FkanVzdGVkJ10pXQpuZXdfcHBwX2RhdGEgPSBuZXdfcHBwX2RhdGEubG9jW35wZC5pc251bGwobmV3X3BwcF9kYXRhWydRXzEwJ10pXQpwcmludChmJ1NoYXBlIG9mIGRhdGEgYWZ0ZXIgZXhjbHVkaW5nIGNvdW50cmllcyBmb3Igd2hpY2ggd2UgZG8gbm90IGhhdmUgUFBQIGRhdGEgJiBOQXMgaW4gUV8xMDoge25ld19wcHBfZGF0YS5zaGFwZX0nKQoKbmV3X3BwcF9kYXRhWydtaW5fc2FsJ10gPSBuZXdfcHBwX2RhdGFbJ1FfMTAnXS5hcHBseShsYW1iZGEgeDogc3RhcnRpbmdfcG9pbnRzW3NhbGFyeV9vcmRlci5pbmRleCh4KV0pIC8gMTAwMDAwMApuZXdfcHBwX2RhdGFbJ21pZF9zYWwnXSA9IG5ld19wcHBfZGF0YVsnUV8xMCddLmFwcGx5KGxhbWJkYSB4OiBtaWRfcG9pbnRzW3NhbGFyeV9vcmRlci5pbmRleCh4KV0pIC8gMTAwMDAwMApuZXdfcHBwX2RhdGFbJ21heF9zYWwnXSA9IG5ld19wcHBfZGF0YVsnUV8xMCddLmFwcGx5KGxhbWJkYSB4OiBlbmRpbmdfcG9pbnRzW3NhbGFyeV9vcmRlci5pbmRleCh4KV0pIC8gMTAwMDAwMApuZXdfcHBwX2RhdGFbJ1BQUF9hZGpfbWluX3NhbCddID0gbmV3X3BwcF9kYXRhWydtaW5fc2FsJ10gKiAoMSArIChuZXdfcHBwX2RhdGFbJ1BQUF9hZGp1c3RlZCddIC8gMTAwLjApKQpuZXdfcHBwX2RhdGFbJ1BQUF9hZGpfbWlkX3NhbCddID0gbmV3X3BwcF9kYXRhWydtaWRfc2FsJ10gKiAoMSArIChuZXdfcHBwX2RhdGFbJ1BQUF9hZGp1c3RlZCddIC8gMTAwLjApKQpuZXdfcHBwX2RhdGFbJ1BQUF9hZGpfbWF4X3NhbCddID0gbmV3X3BwcF9kYXRhWydtYXhfc2FsJ10gKiAoMSArIChuZXdfcHBwX2RhdGFbJ1BQUF9hZGp1c3RlZCddIC8gMTAwLjApKQpuZXdfcHBwX2RhdGFfb25seV9hZGogPSBuZXdfcHBwX2RhdGFbWydQUFBfYWRqX21pbl9zYWwnLCAnUFBQX2Fkal9tYXhfc2FsJ11dCiIKcHlfcnVuX3N0cmluZyhweXRob25fY21kcykKYGBgCgpgYGB7cn0KY29sbmFtZXMocHkkbmV3X3BwcF9kYXRhX29ubHlfYWRqKSA8LSBjKCdsZWZ0JywgJ3JpZ2h0JykKZml0dGVkX2Rpc3RyaWJ1dGlvbl9nYW1tYSA8LSBmaXRkaXN0Y2VucyhweSRuZXdfcHBwX2RhdGFfb25seV9hZGosICJnYW1tYSIpCgojIG92ZXJhbGwgZml0OgpwcmludCgiRml0dGVkIFBhcmFtZXRlcnM6IikKcHJpbnQoZml0dGVkX2Rpc3RyaWJ1dGlvbl9nYW1tYSRlc3RpbWF0ZSkKCiMgTWVhbgptaWRwdF9tZWFuIDwtIHB5JG5ld19wcHBfZGF0YV9vbmx5X2FkaiAlPiUKICBtdXRhdGUobWlkID0gKGxlZnQgKyByZXBsYWNlX25hKHJpZ2h0LCAyKSkgLyAyKSAlPiUKICBzdW1tYXJpc2UoY3J1ZGVfbWVhbiA9IG1lYW4obWlkKSkgJT4lCiAgcHVsbChjcnVkZV9tZWFuKQpwcmludChzcHJpbnRmKCJNaWRwb2ludC1iYXNlZCBNZWFuOiAlZiIsIG1pZHB0X21lYW4pKQplc3RpbV9tZWFuIDwtIGZpdHRlZF9kaXN0cmlidXRpb25fZ2FtbWEkZXN0aW1hdGVbInNoYXBlIl0gLyBmaXR0ZWRfZGlzdHJpYnV0aW9uX2dhbW1hJGVzdGltYXRlWyJyYXRlIl0KcHJpbnQoc3ByaW50ZigiRXRpbWF0ZWQgTWVhbjogJWYiLCBlc3RpbV9tZWFuKSkKCmFscGhhIDwtIGZpdHRlZF9kaXN0cmlidXRpb25fZ2FtbWEkZXN0aW1hdGVbWydzaGFwZSddXQpiZXRhIDwtIGZpdHRlZF9kaXN0cmlidXRpb25fZ2FtbWEkZXN0aW1hdGVbWydyYXRlJ11dCgpicmVha3MgPC0gYygwLCAwLjI1LCAwLjUsIDAuNzUsIDEpCmcgPC0gZ2dwbG90KHB5JG5ld19wcHBfZGF0YV9vbmx5X2FkaikgKyBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRnYW1tYSwgYXJncyA9IGZpdHRlZF9kaXN0cmlidXRpb25fZ2FtbWEkZXN0aW1hdGUsIGNvbG91ciA9IGdyZWVuKSArIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBicmVha3MsIGxhYmVscyA9IHNhcHBseShicmVha3MsIGZ1bmN0aW9uKHgpIHsgcGFzdGUoJyQnLCB4ICogMTAwMDAwMCAvIDEwMDAsICdLJywgc2VwID0gJycpIH0pKQpnIDwtIGcgKwogIGJhc2ljX3Bsb3RfZGVjKCkgKwogIGJhc2ljX2NvbG9yX2RlYyhncmVlbikgKwogIGJhc2ljX29yaWVudF9kZWMoKSArCiAgbGFicyh0aXRsZSA9ICJGaXR0ZWQgR2FtbWEgRGlzdHJpYnV0aW9uIiwgc3VidGl0bGUgPSBzcHJpbnRmKCJBbHBoYT0lLjJmLCBCZXRhPSUuMmYiLCBhbHBoYSwgYmV0YSkpICsKICB4bGFiKCJTYWxhcnkiKSArCiAgeWxhYigiRGlzdHJpYnV0aW9uIikKcHJpbnQoZykKYGBgCgpgYGB7cn0KcHl0aG9uX2NtZHMgPSAiCnBwcF9iaW5zID0gW1sqeF0gZm9yIHggaW4gWypuZXdfcHBwX2RhdGEuZ3JvdXBieShbJ1BQUF9hZGpfbWluX3NhbCcsICdQUFBfYWRqX21heF9zYWwnXSlbJ2thZ2dsZXJfaWQnXS5hZ2dyZWdhdGUoJ2NvdW50JykuaW5kZXhdXQpwcHBfYmlucyA9IHBkLkRhdGFGcmFtZShwcHBfYmlucywgY29sdW1ucyA9IFsnbWluJywgJ21heCddKQoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCgpmaW5kX2FyZWFfbWlkIDwtIGZ1bmN0aW9uKG1pbiwgbWF4LCBhbHBoYSwgYmV0YSkgewogIGZfMSA8LSBwZ2FtbWEobWluLCBzaGFwZSA9IGFscGhhLCByYXRlPSBiZXRhKQogIGZfMiA8LSBwZ2FtbWEobWF4LCBzaGFwZSA9IGFscGhhLCByYXRlID0gYmV0YSkKICBxZ2FtbWEoKGZfMSArIGZfMikgLyAyLjAsIHNoYXBlID0gYWxwaGEsIHJhdGUgPSBiZXRhKQp9CgpmaW5kX2FjdHVhbF9taWQgPC0gZnVuY3Rpb24obWluLCBtYXgsIGFscGhhLCBiZXRhKSB7CiAgc2FtcGxlcyA8LSByZ2FtbWEoMTAwMDAwMCwgc2hhcGUgPSBhbHBoYSwgcmF0ZSA9IGJldGEpCiAgYWN0dWFsX21pZHMgPC0gYygpCiAgZm9yIChpZHggaW4gMTpsZW5ndGgobWluKSkgewogICAgYWN0dWFsX21pZHMgPC0gYyhhY3R1YWxfbWlkcywgbWVhbihzYW1wbGVzWyhzYW1wbGVzID49IG1pbltpZHhdKSAmIChzYW1wbGVzIDw9IG1heFtpZHhdKV0pKQogIH0KICBhY3R1YWxfbWlkcwp9CgpweSRwcHBfYmlucyA8LSBweSRwcHBfYmlucyAlPiUKICBtdXRhdGUoZmluYWxfbWlkID0gZmluZF9hY3R1YWxfbWlkKG1pbiwgbWF4LCBhbHBoYSwgYmV0YSkpCgpweXRob25fY21kcyA9ICIKbmV3X3BwcF9kYXRhID0gcGQubWVyZ2UobmV3X3BwcF9kYXRhLCBwcHBfYmlucywgaG93ID0gJ2lubmVyJywgbGVmdF9vbiA9IFsnUFBQX2Fkal9taW5fc2FsJywgJ1BQUF9hZGpfbWF4X3NhbCddLCByaWdodF9vbiA9IFsnbWluJywgJ21heCddKQoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCmBgYAoKYGBge3J9CmZpZyg4LCAzMCkKZyA8LSBnZ3Bsb3QoZGF0YSA9IHB5JG5ld19wcHBfZGF0YSkgKwogIGdlb21fZGVuc2l0eShhZXMoeCA9IG1pZF9zYWwgKiAxMDAwLCBjb2xvdXIgPSAiT3JpZ2luYWwiKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHN0YXRfZGVuc2l0eShhZXMoeCA9IG1pZF9zYWwgKiAxMDAwLCBjb2xvdXIgPSAiT3JpZ2luYWwiKSwgZ2VvbSA9ICdsaW5lJywgcG9zaXRpb24gPSAnaWRlbnRpdHknKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gZmluYWxfbWlkICogMTAwMCwgY29sb3VyID0gIkFkanVzdGVkIiksIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBzdGF0X2RlbnNpdHkoYWVzKHggPSBmaW5hbF9taWQgKiAxMDAwLCBjb2xvdXIgPSAiQWRqdXN0ZWQiKSwgZ2VvbSA9ICdsaW5lJywgcG9zaXRpb24gPSAnaWRlbnRpdHknKSArCiAgeGxpbSgwLCA3NTApCmcgKwogIGJhc2ljX3Bsb3RfZGVjKCkgKwogIGJhc2ljX2NvbG9yX2RlYyhncmVlbikgKwogIGJhc2ljX29yaWVudF9kZWMoKSArCiAgbGFicyh0aXRsZSA9ICJJbXBhY3Qgb2YgRGlzdHJpYnV0aW9uIEZpdHRpbmciLCBjb2xvdXIgPSAiRGlzdHJpYnV0aW9uIFR5cGUiLCBjYXB0aW9uID0gIihMaW1pdGluZyBzYWxhcnkgdG8gJDc1MEspIikgKwogIHhsYWIoIlNhbGFyeSAoaW4gMTAwSyQpIikgKwogIHlsYWIoIkRlbnNpdHkiKSArCiAgdGhlbWUoCiAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3VyID0gd2hpdGUsIGZhY2UgPSAnYm9sZCcpLAogICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3VyID0gd2hpdGUpLAogICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IGFscGhhKGdyZWVuLCAwLjUpKSwKICAgIGxlZ2VuZC5rZXkgPSBlbGVtZW50X3JlY3QoZmlsbCA9IGFscGhhKGdyZWVuLCAwKSksCiAgICBhc3BlY3QucmF0aW8gPSA3LzE1LAogICAgbGVnZW5kLnBvc2l0aW9uID0gYygwLjg1LCAwLjkpLAogICkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYygiT3JpZ2luYWwiID0gYWxwaGEobGlnaHRfYmx1ZSwgMC42KSwgIkFkanVzdGVkIiA9IGFscGhhKHJlZCwgMC42KSkpCmBgYAoKYGBge3J9CnB5dGhvbl9jbWRzID0gIgpwaXZvdF9kYXRhID0gcGQucGl2b3RfdGFibGUobmV3X2RhdGEuZ3JvdXBieShbJ2hhc19zcGVjX2FjYycsICdRXzEwJ10pW1sna2FnZ2xlcl9pZCddXS5hZ2dyZWdhdGUoJ2NvdW50JyksIHZhbHVlcyA9ICdrYWdnbGVyX2lkJywgaW5kZXggPSBbJ1FfMTAnXSwgY29sdW1ucyA9IFsnaGFzX3NwZWNfYWNjJ10pCmZvciBjb2wgaW4gcGl2b3RfZGF0YS5jb2x1bW5zOgogICAgcGl2b3RfZGF0YVtjb2xdID0gcGl2b3RfZGF0YVtjb2xdIC8gcGl2b3RfZGF0YVtjb2xdLnN1bSgpICogMTAwCnBpdm90X2RhdGEgPSBwaXZvdF9kYXRhLmxvY1tzYWxhcnlfb3JkZXJdCgpoYXNfbm9fc3BlY19hY2MgPSBuZXdfcHBwX2RhdGFbbmV3X3BwcF9kYXRhWydoYXNfc3BlY19hY2MnXSA9PSAwXQpoYXNfc3BlY19hY2MgPSBuZXdfcHBwX2RhdGFbbmV3X3BwcF9kYXRhWydoYXNfc3BlY19hY2MnXSA9PSAxXQpoYXNfbm9fc3BlY19hY2Nfb25seV9hZGogPSBoYXNfbm9fc3BlY19hY2NbWydQUFBfYWRqX21pbl9zYWwnLCAnUFBQX2Fkal9tYXhfc2FsJ11dCmhhc19zcGVjX2FjY19vbmx5X2FkaiA9IGhhc19zcGVjX2FjY1tbJ1BQUF9hZGpfbWluX3NhbCcsICdQUFBfYWRqX21heF9zYWwnXV0KIgpweV9ydW5fc3RyaW5nKHB5dGhvbl9jbWRzKQoKY29sbmFtZXMocHkkaGFzX25vX3NwZWNfYWNjX29ubHlfYWRqKSA8LSBjKCdsZWZ0JywgJ3JpZ2h0JykKZml0dGVkX2Rpc3RyaWJ1dGlvbl9nYW1tYSA8LSBmaXRkaXN0Y2VucyhweSRoYXNfbm9fc3BlY19hY2Nfb25seV9hZGosICJnYW1tYSIpCmFscGhhMSA8LSBmaXR0ZWRfZGlzdHJpYnV0aW9uX2dhbW1hJGVzdGltYXRlW1snc2hhcGUnXV0KYmV0YTEgPC0gZml0dGVkX2Rpc3RyaWJ1dGlvbl9nYW1tYSRlc3RpbWF0ZVtbJ3JhdGUnXV0KCmNvbG5hbWVzKHB5JGhhc19zcGVjX2FjY19vbmx5X2FkaikgPC0gYygnbGVmdCcsICdyaWdodCcpCmZpdHRlZF9kaXN0cmlidXRpb25fZ2FtbWEgPC0gZml0ZGlzdGNlbnMocHkkaGFzX3NwZWNfYWNjX29ubHlfYWRqLCAiZ2FtbWEiKQphbHBoYTIgPC0gZml0dGVkX2Rpc3RyaWJ1dGlvbl9nYW1tYSRlc3RpbWF0ZVtbJ3NoYXBlJ11dCmJldGEyIDwtIGZpdHRlZF9kaXN0cmlidXRpb25fZ2FtbWEkZXN0aW1hdGVbWydyYXRlJ11dCmBgYAoKYGBge3J9CmJyZWFrcyA8LSBjKDAsIDAuMSwgMC4yLCAwLjMsIDAuNCwgMC41KQpnIDwtIGdncGxvdChweSRuZXdfcHBwX2RhdGFfb25seV9hZGopICsKICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRnYW1tYSwgYXJncyA9IGMoc2hhcGUgPSBhbHBoYTEsIHJhdGUgPSBiZXRhMSksIGFlcyhjb2xvdXIgPSAiSGFzIG5vIFNwZWNBY2NzIikpICsKICBzdGF0X2Z1bmN0aW9uKGZ1biA9IGRnYW1tYSwgYXJncyA9IGMoc2hhcGUgPSBhbHBoYTIsIHJhdGUgPSBiZXRhMiksIGFlcyhjb2xvdXIgPSAiSGFzIFNwZWNBY2NzIikpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gYnJlYWtzLCBsYWJlbHMgPSBzYXBwbHkoYnJlYWtzLCBmdW5jdGlvbih4KSB7IHggKiAxMDAwIH0pLCBsaW1pdHMgPSBjKDAsIDAuNSkpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoIkhhcyBubyBTcGVjQWNjcyIgPSBhbHBoYShncmVlbiwgMC42KSwgIkhhcyBTcGVjQWNjcyIgPSBhbHBoYShyZWQsIDAuNikpKQpnIDwtIGcgKwogIGJhc2ljX3Bsb3RfZGVjKCkgKwogIGJhc2ljX2NvbG9yX2RlYyhsaWdodF9ibHVlKSArCiAgYmFzaWNfb3JpZW50X2RlYygpICsKICBsYWJzKHRpdGxlID0gIlNwZWNBY2NzIFVzYWdlIHZzLiBTYWxhcnkiLCBjb2xvdXIgPSAiU3BlY0FjY3MgVXNhZ2UiKSArCiAgeGxhYigiU2FsYXJ5IikgKwogIHlsYWIoIkRpc3RyaWJ1dGlvbiIpICsKICB0aGVtZSgKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSB3aGl0ZSwgZmFjZSA9ICdib2xkJyksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSB3aGl0ZSksCiAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gYWxwaGEobGlnaHRfYmx1ZSwgMC41KSksCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGZpbGwgPSBhbHBoYShsaWdodF9ibHVlLCAwKSksCiAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAuODUsIDAuOSksCiAgKQpwcmludChnKQpgYGAKCmBgYHtyfQpweXRob25fY21kcyA9ICIKaGFzX25vX3NwZWNfYWNjX3NhbGFyaWVzID0gaGFzX25vX3NwZWNfYWNjWydRXzEwJ10udmFsdWVfY291bnRzKCkubG9jW3NhbGFyeV9vcmRlcl0KaGFzX25vX3NwZWNfYWNjX3NhbGFyaWVzX3hfdmFscyA9IGhhc19ub19zcGVjX2FjY19zYWxhcmllcy52YWx1ZXMgLyBoYXNfbm9fc3BlY19hY2Nfc2FsYXJpZXMudmFsdWVzLnN1bSgpICogMTAwCmhhc19zcGVjX2FjY19zYWxhcmllcyA9IGhhc19zcGVjX2FjY1snUV8xMCddLnZhbHVlX2NvdW50cygpLmxvY1tzYWxhcnlfb3JkZXJdCmhhc19zcGVjX2FjY19zYWxhcmllc194X3ZhbHMgPSBoYXNfc3BlY19hY2Nfc2FsYXJpZXMudmFsdWVzIC8gaGFzX3NwZWNfYWNjX3NhbGFyaWVzLnZhbHVlcy5zdW0oKSAqIDEwMApkaWZmID0gaGFzX3NwZWNfYWNjX3NhbGFyaWVzX3hfdmFscyAtIGhhc19ub19zcGVjX2FjY19zYWxhcmllc194X3ZhbHMKIgpweV9ydW5fc3RyaW5nKHB5dGhvbl9jbWRzKQoKc3BlY19hY2Nfc2FsYXJpZXNfcGxvdF9kZiA8LSBhcy5kYXRhLmZyYW1lKGxpc3QocHkkbWlkX3BvaW50cywgcHkkaGFzX3NwZWNfYWNjX3NhbGFyaWVzX3hfdmFscywgcHkkaGFzX25vX3NwZWNfYWNjX3NhbGFyaWVzX3hfdmFscykpCnJvd25hbWVzKHNwZWNfYWNjX3NhbGFyaWVzX3Bsb3RfZGYpIDwtIE5VTEwKY29sbmFtZXMoc3BlY19hY2Nfc2FsYXJpZXNfcGxvdF9kZikgPC0gYygiU2FsYXJ5IiwgIkhhc1NwZWNBY2NzQ291bnQiLCAiSGFzTm9TcGVjQWNjc0NvdW50IikKd2lkdGhzIDwtIHB5JHdpZHRocwoKZmlnKDQsIDMwKQpnIDwtIGdncGxvdChkYXRhID0gc3BlY19hY2Nfc2FsYXJpZXNfcGxvdF9kZiwgYWVzKHggPSBTYWxhcnkpKSArCiAgZ2VvbV9iYXIoYWVzKHkgPSBIYXNTcGVjQWNjc0NvdW50LCBmaWxsID0gJ0hhc1NwZWNBY2NzJyksIHN0YXQgPSAnaWRlbnRpdHknLCBhbHBoYSA9IDAuNSwgd2lkdGggPSB3aWR0aHMpICsKICBnZW9tX2JhcihhZXMoeSA9IEhhc05vU3BlY0FjY3NDb3VudCwgZmlsbCA9ICdIYXNOb1NwZWNBY2NzJyksIHN0YXQgPSAnaWRlbnRpdHknLCBhbHBoYSA9IDAuNSwgd2lkdGggPSB3aWR0aHMpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJIYXNTcGVjQWNjcyIgPSBhbHBoYShsaWdodF9ibHVlLCAwLjYpLCAiSGFzTm9TcGVjQWNjcyIgPSBhbHBoYShyZWQsIDAuNikpKQpnIDwtIGcgKwogIGJhc2ljX3Bsb3RfZGVjKCkgKwogIGJhc2ljX2NvbG9yX2RlYyhncmVlbikgKwogIGJhc2ljX29yaWVudF9kZWMoKSArCiAgbGFicyh0aXRsZSA9ICJTYWxhcnkgRGlzdHJpYnV0aW9uIiwgc3VidGl0bGUgPSAiVHJ1ZSBEaXN0cmlidXRpb24iLCBjYXB0aW9uID0gIihTYWxhcnkgbGltaXRlZCB0byAkMjUwSykiLCBmaWxsID0gIlNwZWNBY2NzIFVzYWdlIikgKwogIHhsYWIoIlNhbGFyeSIpICsKICB5bGFiKCJDb3VudCIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSA4KSwKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSB3aGl0ZSwgZmFjZSA9ICdib2xkJyksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSB3aGl0ZSksCiAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gYWxwaGEoZ3JlZW4sIDAuNSkpLAogICAgbGVnZW5kLmtleSA9IGVsZW1lbnRfcmVjdChmaWxsID0gYWxwaGEoZ3JlZW4sIDApKSwKICAgIGxlZ2VuZC5wb3NpdGlvbiA9IGMoMC44NSwgMC45KSwKICApCnByaW50KGcpCgpzcGVjX2FjY19zYWxhcmllc19wbG90X2RmIDwtIGFzLmRhdGEuZnJhbWUobGlzdChweSRtaWRfcG9pbnRzLCBweSRkaWZmLCBweSRzYWxhcnlfb3JkZXIpKQpyb3duYW1lcyhzcGVjX2FjY19zYWxhcmllc19wbG90X2RmKSA8LSBOVUxMCmNvbG5hbWVzKHNwZWNfYWNjX3NhbGFyaWVzX3Bsb3RfZGYpIDwtIGMoIlNhbGFyeSIsICJEaWZmIiwgIkJpbnMiKQpzcGVjX2FjY19zYWxhcmllc19wbG90X2RmJEJpbnMgPC0gZmFjdG9yKHNwZWNfYWNjX3NhbGFyaWVzX3Bsb3RfZGYkQmlucywgbGV2ZWxzID0gcHkkc2FsYXJ5X29yZGVyKQp3aWR0aHMgPC0gcHkkd2lkdGhzCgpmaWcoNCwgMzApCmcgPC0gZ2dwbG90KGRhdGEgPSBzcGVjX2FjY19zYWxhcmllc19wbG90X2RmLCBhZXMoeCA9IFNhbGFyeSkpICsKICBnZW9tX2JhcihhZXMoeSA9IERpZmYpLCBzdGF0ID0gJ2lkZW50aXR5JywgZmlsbCA9IGdyZWVuLCBhbHBoYSA9IDAuNSwgd2lkdGggPSB3aWR0aHMpCmcgPC0gZyArCiAgYmFzaWNfcGxvdF9kZWMoKSArCiAgYmFzaWNfY29sb3JfZGVjKGdyZWVuKSArCiAgYmFzaWNfb3JpZW50X2RlYygpICsKICBsYWJzKHRpdGxlID0gIlNhbGFyeSBEaXN0cmlidXRpb24iLCBzdWJ0aXRsZSA9ICJUcnVlIERpc3RyaWJ1dGlvbiIsIGNhcHRpb24gPSAiKFNhbGFyeSBsaW1pdGVkIHRvICQyNTBLKSIpICsKICB4bGFiKCJTYWxhcnkiKSArCiAgeWxhYigiQ291bnQiKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksCiAgKQpwcmludChnKQoKZmlnKDQsIDMwKQpnIDwtIGdncGxvdChkYXRhID0gc3BlY19hY2Nfc2FsYXJpZXNfcGxvdF9kZiwgYWVzKHggPSBCaW5zKSkgKwogIGdlb21fYmFyKGFlcyh5ID0gRGlmZiksIHN0YXQgPSAnaWRlbnRpdHknLCBjb2xvdXIgPSBkYXJrX2JsdWUsIGZpbGwgPSBsaWdodF9ibHVlLCBhbHBoYSA9IDAuNSkKZyA8LSBnICsKICBiYXNpY19wbG90X2RlYygpICsKICBiYXNpY19jb2xvcl9kZWMobGlnaHRfYmx1ZSkgKwogIGJhc2ljX29yaWVudF9kZWMoKSArCiAgbGFicyh0aXRsZSA9ICJTYWxhcnkgRGlzdHJpYnV0aW9uIiwgc3VidGl0bGUgPSAiVHJ1ZSBEaXN0cmlidXRpb24iLCBjYXB0aW9uID0gIihTYWxhcnkgbGltaXRlZCB0byAkMjUwSykiKSArCiAgeGxhYigiU2FsYXJ5IikgKwogIHlsYWIoIkNvdW50IikgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpLAogICkKcHJpbnQoZykKCnB5dGhvbl9jbWRzID0gIgpuZXdfcHBwX2RhdGFbJ1BQUF9hZGpfc2FsX2NhdHMnXSA9IG5ld19wcHBfZGF0YVsnZmluYWxfbWlkJ10uYXBwbHkobGFtYmRhIHg6IDAgaWYgKHggKiAxMDAwMDAwIDwgNTAwMCkgZWxzZSAyIGlmICh4ICogMTAwMDAwMCA+IDE3NTAwMCkgZWxzZSAxKQpuZXdfcHBwX2RhdGFbJ1BQUF9hZGpfc2FsX2NhdHMnXSA9IG5ld19wcHBfZGF0YVsnUFBQX2Fkal9zYWxfY2F0cyddLnJlcGxhY2UoezA6ICc8JDUwMDAnLCAxOiAnJDUwMDAtJDE3NTAwMCcsIDI6ICc+JDE3NTAwMCd9KQpjYXRfYXZnX3RhYmxlID0gbmV3X3BwcF9kYXRhLmdyb3VwYnkoJ1BQUF9hZGpfc2FsX2NhdHMnKVsnaGFzX3NwZWNfYWNjJ10uYWdncmVnYXRlKFtucC5uYW5tZWFuLCAnY291bnQnXSkucmVzZXRfaW5kZXgoZHJvcCA9IEZhbHNlKQpjYXRfYXZnX3RhYmxlWyduYW5tZWFuJ10gPSBjYXRfYXZnX3RhYmxlWyduYW5tZWFuJ10uYXBwbHkobGFtYmRhIHg6IG5wLnJvdW5kKHggKiAxMDAwKSAvIDEwMDApCiIKcHlfcnVuX3N0cmluZyhweXRob25fY21kcykKCmcgPC0gZ2d0ZXh0dGFibGUocHkkY2F0X2F2Z190YWJsZSwgcm93cyA9IE5VTEwsIGNvbHMgPSBjKCJTYWxhcnlcbkNhdGVnb3J5IiwgIiUgSGF2aW5nXG5TcGVjQWNjcyIsICJDb3VudCIpLCB0aGVtZSA9IHR0aGVtZSgnbUJsdWUnKSkKZyA8LSB0YWJsZV9jZWxsX2ZvbnQoZywgcm93ID0gMiwgY29sdW1uID0gMiwgZmFjZSA9ICdib2xkJykKZyA8LSB0YWJsZV9jZWxsX2ZvbnQoZywgcm93ID0gMywgY29sdW1uID0gMiwgZmFjZSA9ICdib2xkJykKZyA8LSB0YWJsZV9jZWxsX2ZvbnQoZywgcm93ID0gNCwgY29sdW1uID0gMiwgZmFjZSA9ICdib2xkJykKcHJpbnQoZykKYGBgCgpgYGB7cn0KcHl0aG9uX2NtZHMgPSAiCm5ld19wcHBfZGF0YVsnUFBQX2Fkal9zYWxfY2F0cyddID0gbmV3X3BwcF9kYXRhWydQUFBfYWRqX21pZF9zYWwnXS5hcHBseShsYW1iZGEgeDogMSBpZiAoKHggKiAxMDAwMDAwIDwgNTAwMCkgfCAoeCAqIDEwMDAwMDAgPiAxNzUwMDApKSBlbHNlIDApCnZhbCA9IG5ld19wcHBfZGF0YS5ncm91cGJ5KCdQUFBfYWRqX3NhbF9jYXRzJylbJ2hhc19zcGVjX2FjYyddLmFnZ3JlZ2F0ZShbJ2NvdW50JywgbnAubmFubWVhbl0pCm5ld19wcHBfZGF0YV9vbmx5X2FkaiA9IG5ld19wcHBfZGF0YVtbJ1BQUF9hZGpfc2FsX2NhdHMnLCAnaGFzX3NwZWNfYWNjJ11dCm5ld19wcHBfZGF0YV9vbmx5X2FkalsnUFBQX2Fkal9zYWxfY2F0cyddID0gbmV3X3BwcF9kYXRhX29ubHlfYWRqWydQUFBfYWRqX3NhbF9jYXRzJ10uYXN0eXBlKG5wLmludDMyKQpuZXdfcHBwX2RhdGFfb25seV9hZGpbJ2hhc19zcGVjX2FjYyddID0gbmV3X3BwcF9kYXRhX29ubHlfYWRqWydoYXNfc3BlY19hY2MnXS5hc3R5cGUobnAuaW50MzIpCiIKcHlfcnVuX3N0cmluZyhweXRob25fY21kcykKCnR0ZXN0IDwtIHQudGVzdChoYXNfc3BlY19hY2MgfiBQUFBfYWRqX3NhbF9jYXRzLCBkYXRhID0gcHkkbmV3X3BwcF9kYXRhX29ubHlfYWRqKQpnIDwtIGdncGFyYWdyYXBoKHNwcmludGYoIlNpbXBsZSBwLXRlc3RzIHNob3dlZCB0aGF0IFNwZWNBY2NzIHVzYWdlIGZvciBLYWdnbGVycyBpbiB0aGUgZXh0cmVtZSBzYWxhcnkgcmFuZ2VzIHdhcyBoaWdoZXIgdGhhbiBmb3IgS2FnZ2xlcnMgaW4gdGhlIG5vbi1leHRyZW1lIHNhbGFyeSByYW5nZXMsIHdpdGggdGhlIGRpZmZlcmVuY2UgaW4gdXNhZ2UgYmVpbmcgc3RhdGlzdGljYWxseSBzaWduaWZpY2FudCAocHZhbCA9ICUuMmYpIiwgdHRlc3QkcC52YWx1ZSkpCnByaW50KGcpCmBgYAoKYGBge3J9CnB5dGhvbl9jbWRzID0gIgphZ2Vfb3JkZXIgPSBbCiAgICAnMTgtMjEnLAogICAgJzIyLTI0JywKICAgICcyNS0yOScsCiAgICAnMzAtMzQnLAogICAgJzM1LTM5JywKICAgICc0MC00NCcsCiAgICAnNDUtNDknLAogICAgJzUwLTU0JywKICAgICc1NS01OScsCiAgICAnNjAtNjknLAogICAgJzcwKycsCl0KYWdlcyA9IG5ld19kYXRhWydRXzEnXS52YWx1ZV9jb3VudHMoKS5sb2NbYWdlX29yZGVyXQoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCmBgYAoKYGBge3J9CmFnZV9wbG90X2RmIDwtIGFzLmRhdGEuZnJhbWUobGlzdChweSRhZ2Vfb3JkZXIsIHB5JGFnZXMpKQpyb3duYW1lcyhhZ2VfcGxvdF9kZikgPC0gTlVMTApjb2xuYW1lcyhhZ2VfcGxvdF9kZikgPC0gYygiQWdlIiwgIkNvdW50IikKYWdlX3Bsb3RfZGYkQWdlID0gZmFjdG9yKGFnZV9wbG90X2RmJEFnZSwgbGV2ZWxzID0gcHkkYWdlX29yZGVyKQoKZmlnKDQsIDMwKQpnMSA8LSBnZ3Bsb3QoZGF0YSA9IGFnZV9wbG90X2RmLCBhZXMoeCA9IEFnZSwgeSA9IENvdW50KSkgKyBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JywgY29sb3VyID0gZGFya19ibHVlLCBmaWxsID0gbGlnaHRfYmx1ZSwgYWxwaGEgPSAwLjYpCmcxIDwtIGcxICsKICBiYXNpY19wbG90X2RlYygpICsKICBiYXNpY19jb2xvcl9kZWMobGlnaHRfYmx1ZSkgKwogIGJhc2ljX29yaWVudF9kZWMoKSArCiAgbGFicyh0aXRsZSA9ICJVbmVxdWFsIEJpbiBXaWR0aHMiKSArCiAgeGxhYigiQWdlIEJpbnMiKSArCiAgeWxhYigiQ291bnQiKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gOCksCiAgKQpnMQpgYGAKCmBgYHtyfQpweXRob25fY21kcyA9ICIKc3RhcnRpbmdfcG9pbnRzID0gWzE4LCAyMiwgMjUsIDMwLCAzNSwgNDAsIDQ1LCA1MCwgNTUsIDYwLCA3MF0Kd2lkdGhzID0gICAgICAgICAgWzQgLCAzICwgNSAsIDUgLCA1ICwgNSAsIDUgLCA1ICwgNSAsIDEwLCAxMF0KZW5kaW5nX3BvaW50cyA9IG5wLmFkZChzdGFydGluZ19wb2ludHMsIHdpZHRocykKbWlkX3BvaW50cyA9IG5wLmFkZChzdGFydGluZ19wb2ludHMsIG5wLmRpdmlkZSh3aWR0aHMsIDIpKQoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCmBgYAoKYGBge3J9CmFnZV9wbG90X2RmIDwtIGFzLmRhdGEuZnJhbWUobGlzdChweSRtaWRfcG9pbnRzLCBweSRhZ2VzKSkKcm93bmFtZXMoYWdlX3Bsb3RfZGYpIDwtIE5VTEwKY29sbmFtZXMoYWdlX3Bsb3RfZGYpIDwtIGMoIkFnZSIsICJDb3VudCIpCndpZHRocyA8LSBweSR3aWR0aHMKCmZpZyg0LCAzMCkKZzIgPC0gZ2dwbG90KGRhdGEgPSBhZ2VfcGxvdF9kZiwgYWVzKHggPSBBZ2UsIHkgPSBDb3VudCkpICsgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIGNvbG91ciA9IGJsYWNrLCBmaWxsID0gZ3JlZW4sIGFscGhhID0gMC42LCB3aWR0aCA9IHdpZHRocykKZzIgPC0gZzIgKwogIGJhc2ljX3Bsb3RfZGVjKCkgKwogIGJhc2ljX2NvbG9yX2RlYyhncmVlbikgKwogIGJhc2ljX29yaWVudF9kZWMoKSArCiAgbGFicyh0aXRsZSA9ICJBZ2UgRGlzdHJpYnV0aW9uIiwgc3VidGl0bGUgPSAiVHJ1ZSBEaXN0cmlidXRpb24iLCBjYXB0aW9uID0gIihBZ2UgbGltaXRlZCB0byAkMjUwSykiKSArCiAgeGxhYigiQWdlIikgKwogIHlsYWIoIkNvdW50IikgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpCiAgKQpnMgpgYGAKCmBgYHtyfQpweXRob25fY21kcyA9ICIKbmV3X2FnZV9kYXRhID0gbmV3X2RhdGEuY29weSgpCm5ld19hZ2VfZGF0YVsnbWluX2FnZSddID0gKG5ld19hZ2VfZGF0YVsnUV8xJ10uYXBwbHkobGFtYmRhIHg6IHN0YXJ0aW5nX3BvaW50c1thZ2Vfb3JkZXIuaW5kZXgoeCldKSAtIDE4KSAvIDEwMApuZXdfYWdlX2RhdGFbJ21pZF9hZ2UnXSA9IChuZXdfYWdlX2RhdGFbJ1FfMSddLmFwcGx5KGxhbWJkYSB4OiBtaWRfcG9pbnRzW2FnZV9vcmRlci5pbmRleCh4KV0pIC0gMTgpIC8gMTAwCm5ld19hZ2VfZGF0YVsnbWF4X2FnZSddID0gKG5ld19hZ2VfZGF0YVsnUV8xJ10uYXBwbHkobGFtYmRhIHg6IGVuZGluZ19wb2ludHNbYWdlX29yZGVyLmluZGV4KHgpXSkgLSAxOCkgLyAxMDAKbmV3X2FnZV9kYXRhX29ubHlfYWRqID0gbmV3X2FnZV9kYXRhW1snbWluX2FnZScsICdtYXhfYWdlJ11dCiIKcHlfcnVuX3N0cmluZyhweXRob25fY21kcykKYGBgCgpgYGB7cn0KY29sbmFtZXMocHkkbmV3X2FnZV9kYXRhX29ubHlfYWRqKSA8LSBjKCdsZWZ0JywgJ3JpZ2h0JykKZml0dGVkX2Rpc3RyaWJ1dGlvbl9nYW1tYSA8LSBmaXRkaXN0Y2VucyhweSRuZXdfYWdlX2RhdGFfb25seV9hZGosICJnYW1tYSIpCgphbHBoYSA8LSBmaXR0ZWRfZGlzdHJpYnV0aW9uX2dhbW1hJGVzdGltYXRlW1snc2hhcGUnXV0KYmV0YSA8LSBmaXR0ZWRfZGlzdHJpYnV0aW9uX2dhbW1hJGVzdGltYXRlW1sncmF0ZSddXQpgYGAKCmBgYHtyfQpweXRob25fY21kcyA9ICIKYWdlX2JpbnMgPSBbWyp4XSBmb3IgeCBpbiBbKm5ld19hZ2VfZGF0YS5ncm91cGJ5KFsnbWluX2FnZScsICdtYXhfYWdlJ10pWydrYWdnbGVyX2lkJ10uYWdncmVnYXRlKCdjb3VudCcpLmluZGV4XV0KYWdlX2JpbnMgPSBwZC5EYXRhRnJhbWUoYWdlX2JpbnMsIGNvbHVtbnMgPSBbJ21pbicsICdtYXgnXSkKIgpweV9ydW5fc3RyaW5nKHB5dGhvbl9jbWRzKQoKZmluZF9hY3R1YWxfbWlkIDwtIGZ1bmN0aW9uKG1pbiwgbWF4LCBhbHBoYSwgYmV0YSkgewogIHNhbXBsZXMgPC0gcmdhbW1hKDEwMDAwMDAsIHNoYXBlID0gYWxwaGEsIHJhdGUgPSBiZXRhKQogIGFjdHVhbF9taWRzIDwtIGMoKQogIGZvciAoaWR4IGluIDE6bGVuZ3RoKG1pbikpIHsKICAgIGFjdHVhbF9taWRzIDwtIGMoYWN0dWFsX21pZHMsIG1lYW4oc2FtcGxlc1soc2FtcGxlcyA+PSBtaW5baWR4XSkgJiAoc2FtcGxlcyA8PSBtYXhbaWR4XSldKSkKICB9CiAgYWN0dWFsX21pZHMKfQoKcHkkYWdlX2JpbnMgPC0gcHkkYWdlX2JpbnMgJT4lCiAgbXV0YXRlKGZpbmFsX21pZCA9IGZpbmRfYWN0dWFsX21pZChtaW4sIG1heCwgYWxwaGEsIGJldGEpKQoKcHl0aG9uX2NtZHMgPSAiCm5ld19hZ2VfZGF0YSA9IHBkLm1lcmdlKG5ld19hZ2VfZGF0YSwgYWdlX2JpbnMsIGhvdyA9ICdpbm5lcicsIGxlZnRfb24gPSBbJ21pbl9hZ2UnLCAnbWF4X2FnZSddLCByaWdodF9vbiA9IFsnbWluJywgJ21heCddKQoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCmBgYAoKYGBge3J9CnB5dGhvbl9jbWRzID0gIgpuZXdfYWdlX3BwcF9kYXRhID0gcGQubWVyZ2UobmV3X3BwcF9kYXRhW1sna2FnZ2xlcl9pZCcsICdmaW5hbF9taWQnXV0sIG5ld19hZ2VfZGF0YVtbJ2thZ2dsZXJfaWQnLCAnZmluYWxfbWlkJ11dLCBob3cgPSAnaW5uZXInLCBvbiA9ICdrYWdnbGVyX2lkJywgc3VmZml4ZXMgPSBbJ19zYWxhcnknLCAnX2FnZSddKQoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCgojcHkkbmV3X2FnZV9wcHBfZGF0YSRmaW5hbF9taWRfc2FsYXJ5IDwtIGFzLm51bWVyaWMocHkkbmV3X2FnZV9wcHBfZGF0YSRmaW5hbF9taWRfc2FsYXJ5KQojcHkkbmV3X2FnZV9wcHBfZGF0YSRmaW5hbF9taWRfYWdlIDwtIGFzLm51bWVyaWMocHkkbmV3X2FnZV9wcHBfZGF0YSRmaW5hbF9taWRfYWdlKQpnIDwtIGdncGxvdChkYXRhID0gcHkkbmV3X2FnZV9wcHBfZGF0YSwgYWVzKHggPSBmaW5hbF9taWRfc2FsYXJ5ICogMTAwMDAwMCwgeSA9IGZpbmFsX21pZF9hZ2UgKiAxMDAgKyAxOCkpICsKICBzdGF0X2RlbnNpdHkyZChhZXMoZmlsbCA9IC4uZGVuc2l0eS4uKSwgZ2VvbSA9ICJyYXN0ZXIiLCBjb250b3VyID0gRkFMU0UpICsKICBzY2FsZV94X2xvZzEwKCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gYmxhY2ssIGhpZ2ggPSBsaWdodF9ibHVlKQpnIDwtIGcgKwogIGJhc2ljX3Bsb3RfZGVjKCkgKwogIGJhc2ljX2NvbG9yX2RlYyhsaWdodF9ibHVlKSArCiAgYmFzaWNfb3JpZW50X2RlYygpICsKICBsYWJzKHRpdGxlID0gIlNhbGFyeS1BZ2UgRGVuc2l0eSBQbG90Iiwgc3VidGl0bGUgPSAiVW5kZXJzdGFuZGluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gQWdlICYgU2FsYXJ5IiwgY2FwdGlvbiA9ICIoU2FsYXJ5IGlzIGluIGxvZ2FyaXRobWljIHNjYWxlIGZvciB2aXN1YWxpemF0aW9uKSIsIGZpbGwgPSAiRGVuc2l0eSIpICsKICB4bGFiKCJTYWxhcnkiKSArCiAgeWxhYigiQWdlIikgKwogIHRoZW1lKAogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IHdoaXRlLCBmYWNlID0gJ2JvbGQnKSwKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IHdoaXRlKSwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBhbHBoYShsaWdodF9ibHVlLCAwKSksCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGZpbGwgPSBhbHBoYShsaWdodF9ibHVlLCAwKSksCiAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAuOTUsIDAuNiksCiAgICBsZWdlbmQua2V5LmhlaWdodCA9IHVuaXQoMS41LCAiY20iKSwKICAgIHBhbmVsLmdyaWQubWFqb3IueSA9IGVsZW1lbnRfbGluZShjb2xvciA9IGFscGhhKGJsYWNrLCAwKSksCiAgKQpnCmBgYAoKYGBge3J9CnB5dGhvbl9jbWRzID0gIgpuZXdfYWdlX3BwcF9kYXRhWydmaW5hbF9taWRfc2FsYXJ5X2NhdHMnXSA9IG5ld19hZ2VfcHBwX2RhdGFbJ2ZpbmFsX21pZF9zYWxhcnknXS5hcHBseShsYW1iZGEgeDogJ2xvdycgaWYgeCAqIDEwMDAwMDAgPCA1MDAwIGVsc2UgJ2hpZ2gnIGlmIHggKiAxMDAwMDAwID4gMTc1MDAwIGVsc2UgJ21lZGl1bScpCiIKcHlfcnVuX3N0cmluZyhweXRob25fY21kcykKCnB5JG5ld19hZ2VfcHBwX2RhdGEkZmluYWxfbWlkX3NhbGFyeV9jYXRzIDwtIGZhY3RvcihweSRuZXdfYWdlX3BwcF9kYXRhJGZpbmFsX21pZF9zYWxhcnlfY2F0cywgbGV2ZWxzID0gYygnbG93JywgJ21lZGl1bScsICdoaWdoJykpCmcgPC0gZ2dwbG90KGRhdGEgPSBweSRuZXdfYWdlX3BwcF9kYXRhKSArIGdlb21fYm94cGxvdChhZXMoeCA9IGZpbmFsX21pZF9zYWxhcnlfY2F0cywgeSA9IGZpbmFsX21pZF9hZ2UgKiAxMDAgKyAxOCksIGNvbG91ciA9IGFscGhhKGRhcmtfZ3JlZW4sIDEpLCBmaWxsID0gYWxwaGEoZ3JlZW4sIDAuNyksIG91dGxpZXIuY29sb3IgPSBncmVlbiwgb3V0bGllci5hbHBoYSA9IDAuMDEpCmcgPC0gZyArCiAgYmFzaWNfcGxvdF9kZWMoKSArCiAgYmFzaWNfY29sb3JfZGVjKGdyZWVuKSArCiAgYmFzaWNfb3JpZW50X2RlYygpICsKICBsYWJzKHRpdGxlID0gIkFnZSBmb3IgZGlmZmVyZW50IFNhbGFyeSBDYXRlZ29yaWVzIikgKwogIHhsYWIoIlNhbGFyeSBDYXRlZ29yaWVzIikgKwogIHlsYWIoIkFnZSIpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAwLjUpCiAgKQpnCmBgYAoKYGBge3J9CnB5JHVnX2RhdGEkQWdlIDwtIGZhY3RvcihweSR1Z19kYXRhJEFnZSwgbGV2ZWxzID0gYygiPDIwIiwgIjIwLTIzIiwgIjI0LTI5IiwgIjMwLTM5IiwgIjQwLTQ5IiwgIj49NTAiKSkKcHkkcGdfZGF0YSRBZ2UgPC0gZmFjdG9yKHB5JHBnX2RhdGEkQWdlLCBsZXZlbHMgPSBjKCI8MjAiLCAiMjAtMjMiLCAiMjQtMjkiLCAiMzAtMzkiLCAiNDAtNDkiLCAiPj01MCIpKQpicmVha3MgPC0gYygtMjAsIDAsIDIwLCA0MCkKZyA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9iYXIoZGF0YSA9IHB5JHVnX2RhdGEsIGFlcyh5ID0gQWdlLCB4ID0gQ29tcG9zaXRpb24pLCBzdGF0ID0gJ2lkZW50aXR5JywgY29sb3VyID0gd2hpdGUsIGZpbGwgPSBhbHBoYShsaWdodF9ibHVlLCAwLjYpKSArCiAgZ2VvbV9iYXIoZGF0YSA9IHB5JHBnX2RhdGEsIGFlcyh5ID0gQWdlLCB4ID0gTmVnX0NvbXBvc2l0aW9uKSwgc3RhdCA9ICdpZGVudGl0eScsIGNvbG91ciA9IHdoaXRlLCBmaWxsID0gYWxwaGEoZ3JlZW4sIDAuNikpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAwLCBjb2xvdXIgPSB3aGl0ZSwgc2l6ZSA9IDIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gYnJlYWtzLCBsYWJlbHMgPSBzYXBwbHkoYnJlYWtzLCBmdW5jdGlvbih4KSB7IGFicyh4KSB9KSkKZyA8LSBnICsKICBiYXNpY19wbG90X2RlYygpICsKICBiYXNpY19jb2xvcl9kZWMod2hpdGUpICsKICBiYXNpY19vcmllbnRfZGVjKFRSVUUpICsKICBsYWJzKHRpdGxlID0gIkdlbmVyYWwgU3R1ZGVudCBBZ2UgRGlzdHJpYnV0aW9uIiwgc3VidGl0bGUgPSAiRm9yIHVuZGVyZ3JhZHVhdGUgJiBwb3N0Z3JhZHVhdGUgc3R1ZGVudHMiKSArCiAgeGxhYigiUGVyY2VudGFnZSIpICsKICB5bGFiKCJBZ2UiKQpnCmBgYAoKYGBge3J9CnB5dGhvbl9jbWRzID0gIgpuZXdfcHBwX2NhdHNfZGF0YSA9IHBkLm1lcmdlKG5ld19wcHBfZGF0YSwgbmV3X2FnZV9wcHBfZGF0YSwgaG93ID0gJ2xlZnQnLCBvbiA9ICdrYWdnbGVyX2lkJykKIgpweV9ydW5fc3RyaW5nKHB5dGhvbl9jbWRzKQpgYGAKCmBgYHtyfQpweXRob25fY21kcyA9ICIKY29kaW5nX2V4cF9vcmRlciA9IFsnPCAxIHllYXJzJywgJzEtMyB5ZWFycycsICczLTUgeWVhcnMnLCAnNS0xMCB5ZWFycycsICcxMC0yMCB5ZWFycycsICcyMCsgeWVhcnMnXQpjb2RpbmdfZXhwID0gcGQucGl2b3RfdGFibGUobmV3X3BwcF9jYXRzX2RhdGEuZ3JvdXBieShbJ2ZpbmFsX21pZF9zYWxhcnlfY2F0cycsICdRXzQnXSlbJ2thZ2dsZXJfaWQnXS5hZ2dyZWdhdGUoJ2NvdW50JykucmVzZXRfaW5kZXgoZHJvcCA9IEZhbHNlKSwgaW5kZXggPSAnZmluYWxfbWlkX3NhbGFyeV9jYXRzJywgY29sdW1ucyA9ICdRXzQnKVsna2FnZ2xlcl9pZCddW2NvZGluZ19leHBfb3JkZXJdLmxvY1tbJ2xvdycsICdtZWRpdW0nLCAnaGlnaCddXQpjb2RpbmdfZXhwX3Bsb3RfZGYgPSBjb2RpbmdfZXhwIC8gbnAucmVwZWF0KGNvZGluZ19leHAuc3VtKGF4aXMgPSAxKS52YWx1ZXMsIHJlcGVhdHMgPSA2KS5yZXNoYXBlKCgzLCA2KSkgKiAxMDAKIgpweV9ydW5fc3RyaW5nKHB5dGhvbl9jbWRzKQoKcHkkY29kaW5nX2V4cF9wbG90X2RmIDwtIG1lbHQocHkkY29kaW5nX2V4cF9wbG90X2RmKQpweSRjb2RpbmdfZXhwX3Bsb3RfZGYkaWQgPC0gYygnbG93LXNhbGFyeScsICdtaWQtc2FsYXJ5JywgJ2hpZ2gtc2FsYXJ5JykKcHkkY29kaW5nX2V4cF9wbG90X2RmJGlkIDwtIGZhY3RvcihweSRjb2RpbmdfZXhwX3Bsb3RfZGYkaWQsIGxldmVscyA9IGMoJ2xvdy1zYWxhcnknLCAnbWlkLXNhbGFyeScsICdoaWdoLXNhbGFyeScpKQpweSRjb2RpbmdfZXhwX3Bsb3RfZGYkdGV4dCA8LSBhcHBseShweSRjb2RpbmdfZXhwX3Bsb3RfZGYsIDEsIGZ1bmN0aW9uKHgpIHsgcGFzdGUocm91bmQoYXMubnVtZXJpYyh4Wyd2YWx1ZSddKSAqIDEwMCkgLyAxMDAsICIlIG9mICIsIHhbJ2lkJ10sICIgS2FnZ2xlcnMgaGF2ZVxuIiwgeFsndmFyaWFibGUnXSwgIiBvZiBjb2RpbmcgZXhwZXJpZW5jZSIsIHNlcCA9ICIiKSB9KQpnIDwtIGdncGxvdChkYXRhID0gcHkkY29kaW5nX2V4cF9wbG90X2RmLCBhZXMoeCA9IGlkLCB5ID0gdmFyaWFibGUsIGZpbGwgPSB2YWx1ZSwgdGV4dCA9IHRleHQpKSArIGdlb21fdGlsZSgpICsgdGhlbWVfaXBzdW0oKQpnIDwtIGcgKwogIGJhc2ljX3Bsb3RfZGVjKCkgKwogIGJhc2ljX2NvbG9yX2RlYyhsaWdodF9ibHVlKSArCiAgYmFzaWNfb3JpZW50X2RlYygpICsKICBsYWJzKHRpdGxlID0gIkNvZGluZyBFeHBlcmllbmNlIHZzLiBTYWxhcnkiLCBzdWJ0aXRsZSA9ICJIZWF0bWFwIiwgZmlsbCA9ICJQZXJjZW50YWdlIikgKwogIHhsYWIoIlNhbGFyeSBDYXRlZ29yeSIpICsKICB5bGFiKCJDb2RpbmcgRXhwZXJpZW5jZSIpICsKICB0aGVtZSgKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSB3aGl0ZSwgZmFjZSA9ICdib2xkJyksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSB3aGl0ZSksCiAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gYWxwaGEobGlnaHRfYmx1ZSwgMCkpLAogICAgbGVnZW5kLmtleSA9IGVsZW1lbnRfcmVjdChmaWxsID0gYWxwaGEobGlnaHRfYmx1ZSwgMCkpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gYygwLjk1LCAwLjYpLAogICAgbGVnZW5kLmtleS5oZWlnaHQgPSB1bml0KDEuNSwgImNtIiksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDApCiAgKQpnIDwtIGdncGxvdGx5KGcsIHRvb2x0aXA9InRleHQiKQpnCmBgYAoKYGBge3J9CnB5dGhvbl9jbWRzID0gIgpuZXdfcHBwX2NhdHNfZGF0YVsnUV82J10gPSBuZXdfcHBwX2NhdHNfZGF0YVsnUV82J10ubWFwKHsKICAgICc1LTEwIHllYXJzJzogJzUtMTAnLAogICAgJzIwIG9yIG1vcmUgeWVhcnMnOiAnMjArJywKICAgICczLTQgeWVhcnMnOiAnMy00JywKICAgICc0LTUgeWVhcnMnOiAnNC01JywKICAgICdJIGRvIG5vdCB1c2UgbWFjaGluZSBsZWFybmluZyBtZXRob2RzJzogJzwxJywKICAgICdVbmRlciAxIHllYXInOiAnPDEnLAogICAgJzItMyB5ZWFycyc6ICcyLTMnLAogICAgJzEtMiB5ZWFycyc6ICcxLTInLAogICAgJzEwLTIwIHllYXJzJzogJzEwLTIwJywKfSkKCm1sX2V4cF9vcmRlciA9IFsnPDEnLCAnMS0yJywgJzItMycsICczLTQnLCAnNC01JywgJzUtMTAnLCAnMTAtMjAnLCAnMjArJ10KbWxfZXhwID0gcGQucGl2b3RfdGFibGUobmV3X3BwcF9jYXRzX2RhdGEuZ3JvdXBieShbJ2ZpbmFsX21pZF9zYWxhcnlfY2F0cycsICdRXzYnXSlbJ2thZ2dsZXJfaWQnXS5hZ2dyZWdhdGUoJ2NvdW50JykucmVzZXRfaW5kZXgoZHJvcCA9IEZhbHNlKSwgaW5kZXggPSAnZmluYWxfbWlkX3NhbGFyeV9jYXRzJywgY29sdW1ucyA9ICdRXzYnKVsna2FnZ2xlcl9pZCddW21sX2V4cF9vcmRlcl0ubG9jW1snbG93JywgJ21lZGl1bScsICdoaWdoJ11dCm1sX2V4cF9wbG90X2RmID0gbWxfZXhwIC8gbnAucmVwZWF0KG1sX2V4cC5zdW0oYXhpcyA9IDEpLnZhbHVlcywgcmVwZWF0cyA9IDgpLnJlc2hhcGUoKDMsIDgpKSAqIDEwMAoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCgpweSRtbF9leHBfcGxvdF9kZiA8LSBtZWx0KHB5JG1sX2V4cF9wbG90X2RmKQpweSRtbF9leHBfcGxvdF9kZiRpZCA8LSBjKCdsb3ctc2FsYXJ5JywgJ21pZC1zYWxhcnknLCAnaGlnaC1zYWxhcnknKQpweSRtbF9leHBfcGxvdF9kZiRpZCA8LSBmYWN0b3IocHkkbWxfZXhwX3Bsb3RfZGYkaWQsIGxldmVscyA9IGMoJ2xvdy1zYWxhcnknLCAnbWlkLXNhbGFyeScsICdoaWdoLXNhbGFyeScpKQpweSRtbF9leHBfcGxvdF9kZiR0ZXh0IDwtIGFwcGx5KHB5JG1sX2V4cF9wbG90X2RmLCAxLCBmdW5jdGlvbih4KSB7IHBhc3RlKHJvdW5kKGFzLm51bWVyaWMoeFsndmFsdWUnXSkgKiAxMDApIC8gMTAwLCAiJSBvZiAiLCB4WydpZCddLCAiIEthZ2dsZXJzIGhhdmVcbiIsIHhbJ3ZhcmlhYmxlJ10sICIgb2YgTUwgZXhwZXJpZW5jZSIsIHNlcCA9ICIiKSB9KQpnIDwtIGdncGxvdChkYXRhID0gcHkkbWxfZXhwX3Bsb3RfZGYsIGFlcyh4ID0gaWQsIHkgPSB2YXJpYWJsZSwgZmlsbCA9IHZhbHVlLCB0ZXh0ID0gdGV4dCkpICsgZ2VvbV90aWxlKCkgKyB0aGVtZV9pcHN1bSgpCmcgPC0gZyArCiAgYmFzaWNfcGxvdF9kZWMoKSArCiAgYmFzaWNfY29sb3JfZGVjKGxpZ2h0X2JsdWUpICsKICBiYXNpY19vcmllbnRfZGVjKCkgKwogIGxhYnModGl0bGUgPSAiTUwgRXhwZXJpZW5jZSB2cy4gU2FsYXJ5Iiwgc3VidGl0bGUgPSAiSGVhdG1hcCIsIGZpbGwgPSAiUGVyY2VudGFnZSIpICsKICB4bGFiKCJTYWxhcnkgQ2F0ZWdvcnkiKSArCiAgeWxhYigiTUwgRXhwZXJpZW5jZSIpICsKICB0aGVtZSgKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSB3aGl0ZSwgZmFjZSA9ICdib2xkJyksCiAgICBsZWdlbmQudGV4dCA9IGVsZW1lbnRfdGV4dChjb2xvdXIgPSB3aGl0ZSksCiAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gYWxwaGEobGlnaHRfYmx1ZSwgMCkpLAogICAgbGVnZW5kLmtleSA9IGVsZW1lbnRfcmVjdChmaWxsID0gYWxwaGEobGlnaHRfYmx1ZSwgMCkpLAogICAgbGVnZW5kLnBvc2l0aW9uID0gYygwLjk1LCAwLjYpLAogICAgbGVnZW5kLmtleS5oZWlnaHQgPSB1bml0KDEuNSwgImNtIiksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDApCiAgKQpnIDwtIGdncGxvdGx5KGcsIHRvb2x0aXA9InRleHQiKQpnCmBgYAoKYGBge3J9CnB5dGhvbl9jbWRzID0gIgpuZXdfcHBwX2NhdHNfZGF0YVsnUV8xMSddID0gbmV3X3BwcF9jYXRzX2RhdGFbJ1FfMTEnXS5tYXAoewogICAgJyQxMDAtJDk5OSc6ICcxMDAtOTk5JywKICAgICckMTAsMDAwLSQ5OSw5OTknOiAnMTAwMDAtOTk5OTknLAogICAgJyQxMDAwLSQ5LDk5OSc6ICcxMDAwLTk5OTknLAogICAgJyQwICgkVVNEKSc6ICcwJywKICAgICckMS0kOTknOiAnMS05OScsCiAgICAnJDEwMCwwMDAgb3IgbW9yZSAoJFVTRCknOiAnPjEwMDAwMCcKfSkKbW9uZXlfc3BlbnRfb3JkZXIgPSBbJzAnLCAnMS05OScsICcxMDAtOTk5JywgJzEwMDAtOTk5OScsICcxMDAwMC05OTk5OScsICc+MTAwMDAwJ10KbW9uZXlfc3BlbnQgPSBwZC5waXZvdF90YWJsZShuZXdfcHBwX2NhdHNfZGF0YS5ncm91cGJ5KFsnZmluYWxfbWlkX3NhbGFyeV9jYXRzJywgJ1FfMTEnXSlbJ2thZ2dsZXJfaWQnXS5hZ2dyZWdhdGUoJ2NvdW50JykucmVzZXRfaW5kZXgoZHJvcCA9IEZhbHNlKSwgaW5kZXggPSAnZmluYWxfbWlkX3NhbGFyeV9jYXRzJywgY29sdW1ucyA9ICdRXzExJylbJ2thZ2dsZXJfaWQnXVttb25leV9zcGVudF9vcmRlcl0ubG9jW1snbG93JywgJ21lZGl1bScsICdoaWdoJ11dCm1vbmV5X3NwZW50X3Bsb3RfZGYgPSBtb25leV9zcGVudCAvIG5wLnJlcGVhdChtb25leV9zcGVudC5zdW0oYXhpcyA9IDEpLnZhbHVlcywgcmVwZWF0cyA9IDYpLnJlc2hhcGUoKDMsIDYpKSAqIDEwMAoiCnB5X3J1bl9zdHJpbmcocHl0aG9uX2NtZHMpCgpweSRtb25leV9zcGVudF9wbG90X2RmIDwtIG1lbHQocHkkbW9uZXlfc3BlbnRfcGxvdF9kZikKcHkkbW9uZXlfc3BlbnRfcGxvdF9kZiRpZCA8LSBjKCdsb3ctc2FsYXJ5JywgJ21pZC1zYWxhcnknLCAnaGlnaC1zYWxhcnknKQpweSRtb25leV9zcGVudF9wbG90X2RmJGlkIDwtIGZhY3RvcihweSRtb25leV9zcGVudF9wbG90X2RmJGlkLCBsZXZlbHMgPSBjKCdsb3ctc2FsYXJ5JywgJ21pZC1zYWxhcnknLCAnaGlnaC1zYWxhcnknKSkKcHkkbW9uZXlfc3BlbnRfcGxvdF9kZiR0ZXh0IDwtIGFwcGx5KHB5JG1vbmV5X3NwZW50X3Bsb3RfZGYsIDEsIGZ1bmN0aW9uKHgpIHsgcGFzdGUocm91bmQoYXMubnVtZXJpYyh4Wyd2YWx1ZSddKSAqIDEwMCkgLyAxMDAsICIlIG9mICIsIHhbJ2lkJ10sICIgS2FnZ2xlcnMgaGF2ZVxuc3BlbnQgJCIsIHhbJ3ZhcmlhYmxlJ10sICIgb24gTUwgc2VydmljZXMiLCBzZXAgPSAiIikgfSkKZyA8LSBnZ3Bsb3QoZGF0YSA9IHB5JG1vbmV5X3NwZW50X3Bsb3RfZGYsIGFlcyh4ID0gaWQsIHkgPSB2YXJpYWJsZSwgZmlsbCA9IHZhbHVlLCB0ZXh0ID0gdGV4dCkpICsgZ2VvbV90aWxlKCkgKyB0aGVtZV9pcHN1bSgpCmcgPC0gZyArCiAgYmFzaWNfcGxvdF9kZWMoKSArCiAgYmFzaWNfY29sb3JfZGVjKGxpZ2h0X2JsdWUpICsKICBiYXNpY19vcmllbnRfZGVjKCkgKwogIGxhYnModGl0bGUgPSAiTW9uZXkgU3BlbnQgb24gTUwgc2VydmljZXMgdnMuIFNhbGFyeSIsIHN1YnRpdGxlID0gIkhlYXRtYXAiLCBmaWxsID0gIlBlcmNlbnRhZ2UiKSArCiAgeGxhYigiU2FsYXJ5IENhdGVnb3J5IikgKwogIHlsYWIoIk1vbmV5IFNwZW50IikgKwogIHRoZW1lKAogICAgbGVnZW5kLnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IHdoaXRlLCBmYWNlID0gJ2JvbGQnKSwKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IHdoaXRlKSwKICAgIGxlZ2VuZC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSBhbHBoYShsaWdodF9ibHVlLCAwKSksCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGZpbGwgPSBhbHBoYShsaWdodF9ibHVlLCAwKSksCiAgICBsZWdlbmQucG9zaXRpb24gPSBjKDAuOTUsIDAuNiksCiAgICBsZWdlbmQua2V5LmhlaWdodCA9IHVuaXQoMS41LCAiY20iKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG91ciA9IHdoaXRlKQogICkKZyA8LSBnZ3Bsb3RseShnLCB0b29sdGlwPSJ0ZXh0IikKZwpgYGAKCg==